use clap::ValueEnum;
use serde::{Serialize, Serializer, ser::SerializeMap};
use crate::model::{
Comment, Expression, FunctionDeclItem, FunctionDefItem, GObjectType, SourceLocation, Statement,
TypeDefItem, VariableDecl,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, ValueEnum)]
#[serde(rename_all = "snake_case")]
pub enum TopLevelItemKind {
FunctionDefinition,
FunctionDeclaration,
Typedef,
Struct,
Enum,
Include,
Define,
GObjectType,
Conditional,
GObjectDeclsBlock,
Declaration,
Expression,
Other,
}
#[derive(Debug, Clone)]
pub enum TopLevelItem {
Preprocessor(PreprocessorDirective),
TypeDefinition(TypeDefItem),
FunctionDeclaration(FunctionDeclItem),
FunctionDefinition(FunctionDefItem),
Declaration(Box<VariableDecl>),
Expression(Box<Expression>),
Comment(Comment),
}
impl TopLevelItem {
pub fn name(&self) -> Option<&str> {
match self {
Self::FunctionDefinition(f) => Some(&f.name),
Self::FunctionDeclaration(f) => Some(&f.name),
Self::TypeDefinition(td) => match td {
TypeDefItem::Typedef { name, .. } | TypeDefItem::Struct { name, .. } => Some(name),
TypeDefItem::Enum(enum_info) => enum_info.name.as_deref(),
},
Self::Preprocessor(PreprocessorDirective::Include { path, .. }) => Some(path),
Self::Preprocessor(PreprocessorDirective::Define { name, .. }) => Some(name),
Self::Preprocessor(PreprocessorDirective::GObjectType(gobject_type)) => {
Some(&gobject_type.type_name)
}
Self::Declaration(decl) => Some(&decl.name),
Self::Comment(_) => None,
_ => None,
}
}
pub fn kind(&self) -> TopLevelItemKind {
match self {
Self::FunctionDefinition(_) => TopLevelItemKind::FunctionDefinition,
Self::FunctionDeclaration(_) => TopLevelItemKind::FunctionDeclaration,
Self::TypeDefinition(TypeDefItem::Typedef { .. }) => TopLevelItemKind::Typedef,
Self::TypeDefinition(TypeDefItem::Struct { .. }) => TopLevelItemKind::Struct,
Self::TypeDefinition(TypeDefItem::Enum(_)) => TopLevelItemKind::Enum,
Self::Preprocessor(PreprocessorDirective::Include { .. }) => TopLevelItemKind::Include,
Self::Preprocessor(PreprocessorDirective::Define { .. }) => TopLevelItemKind::Define,
Self::Preprocessor(PreprocessorDirective::GObjectType { .. }) => {
TopLevelItemKind::GObjectType
}
Self::Preprocessor(PreprocessorDirective::Conditional { .. }) => {
TopLevelItemKind::Conditional
}
Self::Preprocessor(PreprocessorDirective::GObjectDeclsBlock { .. }) => {
TopLevelItemKind::GObjectDeclsBlock
}
Self::Declaration(_) => TopLevelItemKind::Declaration,
Self::Expression(_) => TopLevelItemKind::Expression,
Self::Comment(_) => TopLevelItemKind::Other,
Self::Preprocessor(_) => TopLevelItemKind::Other,
}
}
pub fn has_doc(&self) -> bool {
match self {
Self::FunctionDefinition(f) => f.doc.is_some(),
Self::FunctionDeclaration(f) => f.doc.is_some(),
Self::TypeDefinition(td) => match td {
TypeDefItem::Struct { doc, .. } | TypeDefItem::Typedef { doc, .. } => doc.is_some(),
TypeDefItem::Enum(e) => e.doc.is_some(),
},
_ => false,
}
}
pub(crate) fn is_comment_at_byte(&self, byte: usize) -> bool {
matches!(self, Self::Comment(c) if c.location.start_byte == byte)
}
}
impl Serialize for TopLevelItem {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
match self {
Self::Preprocessor(directive) => directive.serialize(s),
Self::TypeDefinition(v) => {
let mut m = s.serialize_map(Some(1))?;
m.serialize_entry("type_definition", v)?;
m.end()
}
Self::FunctionDeclaration(v) => {
let mut m = s.serialize_map(Some(1))?;
m.serialize_entry("function_declaration", v)?;
m.end()
}
Self::FunctionDefinition(v) => {
let mut m = s.serialize_map(Some(1))?;
m.serialize_entry("function_definition", v)?;
m.end()
}
Self::Declaration(v) => {
let mut m = s.serialize_map(Some(1))?;
m.serialize_entry("declaration", v)?;
m.end()
}
Self::Expression(v) => {
let mut m = s.serialize_map(Some(1))?;
m.serialize_entry("expression", v)?;
m.end()
}
Self::Comment(v) => {
let mut m = s.serialize_map(Some(1))?;
m.serialize_entry("comment", v)?;
m.end()
}
}
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum PragmaKind {
Once,
DiagnosticPush,
DiagnosticPop,
DiagnosticIgnored { warning: String },
Other {
name: String,
arguments: Option<String>,
},
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum PreprocessorDirective {
Include {
path: String,
is_system: bool,
location: SourceLocation,
},
Define {
name: String,
value: Option<DefineValue>,
location: SourceLocation,
},
Call {
directive: String,
location: SourceLocation,
},
Pragma {
kind: PragmaKind,
location: SourceLocation,
},
GObjectType(Box<GObjectType>),
AutoptrCleanupFunc {
type_name: String,
cleanup_function: String,
location: SourceLocation,
},
AutoCleanupClearFunc {
type_name: String,
cleanup_function: String,
location: SourceLocation,
},
MacroWithCode {
macro_name: String,
arguments: Vec<String>,
code_statements: Vec<Statement>,
location: SourceLocation,
},
Conditional {
kind: ConditionalKind,
condition: Option<String>,
body: Vec<TopLevelItem>,
location: SourceLocation,
},
GObjectDeclsBlock {
body: Vec<TopLevelItem>,
location: SourceLocation,
},
}
impl PreprocessorDirective {
pub fn location(&self) -> &SourceLocation {
match self {
Self::Include { location, .. }
| Self::Define { location, .. }
| Self::Call { location, .. }
| Self::Pragma { location, .. }
| Self::AutoptrCleanupFunc { location, .. }
| Self::AutoCleanupClearFunc { location, .. }
| Self::MacroWithCode { location, .. }
| Self::Conditional { location, .. }
| Self::GObjectDeclsBlock { location, .. } => location,
Self::GObjectType(gt) => >.location,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ConditionalKind {
Ifdef,
Ifndef,
If,
Elif,
Else,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum DefineValue {
StringLiteral(String),
Number(String),
Identifier(String),
Expression(String),
}
impl DefineValue {
pub fn parse(raw: &str) -> Self {
let trimmed = raw.trim();
if trimmed.starts_with('"') && trimmed.ends_with('"') {
return Self::StringLiteral(trimmed[1..trimmed.len() - 1].to_owned());
}
if trimmed
.bytes()
.all(|b| b.is_ascii_digit() || b == b'x' || b == b'X' || b == b'-')
&& !trimmed.is_empty()
{
return Self::Number(trimmed.to_owned());
}
if trimmed
.bytes()
.all(|b| b.is_ascii_alphanumeric() || b == b'_')
&& !trimmed.is_empty()
{
return Self::Identifier(trimmed.to_owned());
}
Self::Expression(trimmed.to_owned())
}
pub fn as_raw_str(&self) -> &str {
match self {
Self::StringLiteral(s)
| Self::Number(s)
| Self::Identifier(s)
| Self::Expression(s) => s,
}
}
}