gobject-ast 0.1.1

AST representation of GObject C code
Documentation
mod assignment;
mod binary;
mod call;
mod cast;
mod conditional;
mod field_access;
mod identifier;
mod initializer_list;
mod literal;
mod offsetof;
mod sizeof;
mod subscript;
mod unary;
mod update;

pub use assignment::Assignment;
pub use binary::BinaryExpression;
pub use call::{Argument, CallExpression};
pub use cast::CastExpression;
pub use conditional::ConditionalExpression;
pub use field_access::FieldAccessExpression;
pub use identifier::IdentifierExpression;
pub use initializer_list::{Designator, InitializerItem, InitializerListExpression};
pub use literal::{
    BooleanExpression, CharLiteralExpression, CommentExpression, GenericExpression, NullExpression,
    NumberLiteralExpression, StringLiteralExpression,
};
pub use offsetof::{OffsetOfExpression, StructField};
use serde::Serialize;
pub use sizeof::{SizeofExpression, SizeofOperand};
pub use subscript::SubscriptExpression;
pub use unary::UnaryExpression;
pub use update::UpdateExpression;

use crate::model::SourceLocation;

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Expression {
    Call(CallExpression),
    Assignment(Assignment),
    Binary(BinaryExpression),
    Unary(UnaryExpression),
    Identifier(IdentifierExpression),
    FieldAccess(FieldAccessExpression),
    StringLiteral(StringLiteralExpression),
    NumberLiteral(NumberLiteralExpression),
    Null(NullExpression),
    Boolean(BooleanExpression),
    Cast(CastExpression),
    Conditional(ConditionalExpression),
    Sizeof(SizeofExpression),
    Subscript(SubscriptExpression),
    InitializerList(InitializerListExpression),
    CharLiteral(CharLiteralExpression),
    Update(UpdateExpression),
    Comment(CommentExpression),
    OffsetOf(OffsetOfExpression),
    Generic(GenericExpression),
}

impl Expression {
    pub fn location(&self) -> &SourceLocation {
        match self {
            Self::Call(c) => &c.location,
            Self::Assignment(a) => &a.location,
            Self::Binary(b) => &b.location,
            Self::Unary(u) => &u.location,
            Self::Identifier(i) => &i.location,
            Self::FieldAccess(f) => &f.location,
            Self::StringLiteral(s) => &s.location,
            Self::NumberLiteral(n) => &n.location,
            Self::Null(n) => &n.location,
            Self::Boolean(b) => &b.location,
            Self::Cast(c) => &c.location,
            Self::Conditional(c) => &c.location,
            Self::Sizeof(s) => &s.location,
            Self::Subscript(s) => &s.location,
            Self::InitializerList(i) => &i.location,
            Self::CharLiteral(c) => &c.location,
            Self::Update(u) => &u.location,
            Self::Comment(c) => &c.location,
            Self::OffsetOf(o) => &o.location,
            Self::Generic(g) => &g.location,
        }
    }

    /// Convert this expression back to source text
    pub fn to_source_string<'a>(&self, source: &'a [u8]) -> Option<&'a str> {
        let loc = self.location();
        std::str::from_utf8(&source[loc.start_byte..loc.end_byte]).ok()
    }

    /// Recursively walk all nested expressions. The closure receives a
    /// `&'s Expression` tied to `self`'s lifetime, so references extracted
    /// inside the closure can be stored in an outer `Vec<&'s T>`.
    pub fn walk<'s, F>(&'s self, f: &mut F)
    where
        F: FnMut(&'s Self),
    {
        f(self);
        match self {
            Self::Call(call) => {
                call.function.walk(f);
                for arg in &call.arguments {
                    let Argument::Expression(e) = arg;
                    e.walk(f);
                }
            }
            Self::Assignment(assign) => {
                assign.lhs.walk(f);
                assign.rhs.walk(f);
            }
            Self::Unary(unary) => {
                unary.operand.walk(f);
            }
            Self::Binary(binary) => {
                binary.left.walk(f);
                binary.right.walk(f);
            }
            Self::Cast(cast) => {
                cast.operand.walk(f);
            }
            Self::Conditional(cond) => {
                cond.condition.walk(f);
                cond.then_expr.walk(f);
                cond.else_expr.walk(f);
            }
            Self::Subscript(subscript) => {
                subscript.array.walk(f);
                subscript.index.walk(f);
            }
            Self::Update(update) => {
                update.operand.walk(f);
            }
            Self::FieldAccess(field) => {
                field.base.walk(f);
            }
            Self::InitializerList(init) => {
                for item in &init.items {
                    if let Some(Designator::Subscript(idx)) = &item.designator {
                        idx.walk(f);
                    }
                    item.value.walk(f);
                }
            }
            Self::Identifier(_)
            | Self::StringLiteral(_)
            | Self::NumberLiteral(_)
            | Self::Null(_)
            | Self::Boolean(_)
            | Self::Sizeof(_)
            | Self::CharLiteral(_)
            | Self::Comment(_)
            | Self::OffsetOf(_)
            | Self::Generic(_) => {}
        }
    }

    /// Extract variable name from simple expressions (Identifier or
    /// FieldAccess)
    pub fn extract_variable_name<'a>(&self, source: &'a [u8]) -> Option<&'a str> {
        match self {
            Self::Identifier(_) | Self::FieldAccess(_) => self.location().as_str(source),
            _ => None,
        }
    }

    /// Check if this expression is NULL
    /// Handles both Expression::Null and the identifier "NULL" (common in C
    /// code)
    pub fn is_null(&self) -> bool {
        matches!(self, Self::Null(_)) || matches!(self, Self::Identifier(id) if id.name == "NULL")
    }

    /// Check if this expression is the number 0
    pub fn is_zero(&self) -> bool {
        matches!(self, Self::NumberLiteral(n) if n.value.trim() == "0")
    }

    /// Check if this expression is a string literal
    pub fn is_string_literal(&self) -> bool {
        matches!(self, Self::StringLiteral(_))
    }

    /// Extract string literal value, unwrapping macro calls like I_("string")
    /// Returns the string without quotes
    pub fn extract_string_value(&self) -> Option<String> {
        match self {
            Self::StringLiteral(lit) => Some(lit.value.trim_matches('"').to_string()),
            _ => None,
        }
    }

    /// Check if this is a string literal or a macro wrapping a string literal
    pub fn is_string_or_macro_string(&self) -> bool {
        self.extract_string_value().is_some()
    }

    /// Check if this expression contains an identifier with the given name
    /// Recursively searches through the entire expression tree
    pub fn contains_identifier(&self, name: &str) -> bool {
        let mut found = false;
        self.walk(&mut |e| {
            if let Self::Identifier(id) = e
                && id.name == name
            {
                found = true;
            }
        });
        found
    }

    /// Collect all identifiers in this expression
    /// Returns a list of all identifier names found in the expression tree
    pub fn collect_identifiers(&self) -> Vec<String> {
        let mut identifiers = Vec::new();
        self.walk(&mut |e| {
            if let Self::Identifier(id) = e {
                identifiers.push(id.name.clone());
            }
        });
        identifiers
    }

    /// Check if this expression is a call to the specified function
    pub fn is_call_to(&self, function_name: &str) -> bool {
        matches!(self, Self::Call(call) if call.is_function(function_name))
    }

    /// Check if this expression is a call to any of the specified functions
    pub fn is_call_to_any(&self, function_names: &[&str]) -> bool {
        matches!(self, Self::Call(call) if call.function_name_str().is_some_and(|name| function_names.contains(&name)))
    }
}