use crate::c::Include;
use crate::common::{Doc, Ident, Literal, Path};
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct File {
pub doc: Doc,
pub pragma_once: bool,
pub includes: Vec<Include>,
pub decls: Vec<Decl>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Decl {
Namespace(Namespace),
Class(Class),
Enum(Enum),
Alias(Alias),
Function(Function),
Variable(Variable),
Template(Template),
Comment(Doc),
Verbatim(String),
Forward {
is_struct: bool,
name: Ident,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Namespace {
pub doc: Doc,
pub name: Ident,
pub decls: Vec<Decl>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Class {
pub doc: Doc,
pub is_struct: bool,
pub name: Ident,
pub bases: Vec<Base>,
pub sections: Vec<Section>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Base {
pub access: Access,
pub virtual_: bool,
pub path: Path,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Section {
pub access: Access,
pub members: Vec<ClassMember>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Access {
Public,
Protected,
Private,
}
impl Access {
pub const ALL: [Self; 3] = [Self::Public, Self::Protected, Self::Private];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Public => "public",
Self::Protected => "protected",
Self::Private => "private",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ClassMember {
Field(Field),
Method(Method),
Constructor(Constructor),
Destructor(Destructor),
Nested(Box<Decl>),
Alias(Alias),
Comment(Doc),
Verbatim(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Field {
pub doc: Doc,
pub type_: Type,
pub name: Ident,
pub dims: Vec<Expr>,
pub default: Option<Expr>,
pub is_static: bool,
pub is_constexpr: bool,
pub bits: Option<u16>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Method {
pub doc: Doc,
pub returns: Type,
pub name: Ident,
pub params: Vec<Param>,
pub is_const: bool,
pub is_static: bool,
pub is_virtual: bool,
pub is_pure: bool,
pub is_constexpr: bool,
pub is_noexcept: bool,
pub is_override: bool,
pub body: Option<Block>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Constructor {
pub doc: Doc,
pub name: Ident,
pub params: Vec<Param>,
pub initialisers: Vec<(Ident, Expr)>,
pub is_explicit: bool,
pub is_constexpr: bool,
pub is_noexcept: bool,
pub body: FunctionBody,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Destructor {
pub doc: Doc,
pub name: Ident,
pub is_virtual: bool,
pub body: FunctionBody,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FunctionBody {
Block(Block),
Defaulted,
Deleted,
None,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Enum {
pub doc: Doc,
pub is_scoped: bool,
pub name: Ident,
pub underlying: Option<Type>,
pub enumerators: Vec<Enumerator>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Enumerator {
pub doc: Doc,
pub name: Ident,
pub value: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Alias {
pub doc: Doc,
pub name: Ident,
pub type_: Type,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Function {
pub doc: Doc,
pub returns: Type,
pub name: Ident,
pub params: Vec<Param>,
pub is_inline: bool,
pub is_static: bool,
pub is_constexpr: bool,
pub is_noexcept: bool,
pub is_maybe_unused: bool,
pub body: FunctionBody,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Variable {
pub doc: Doc,
pub type_: Type,
pub name: Ident,
pub value: Option<Expr>,
pub is_static: bool,
pub is_constexpr: bool,
pub is_inline: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Param {
pub type_: Type,
pub name: Option<Ident>,
pub default: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Template {
pub doc: Doc,
pub params: Vec<TemplateParam>,
pub decl: Box<Decl>,
pub specialisation: Vec<Type>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TemplateParam {
pub kind: TemplateParamKind,
pub name: Ident,
pub default: Option<Type>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TemplateParamKind {
TypeName,
Class,
NonType(Box<Type>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Type {
Primitive(Primitive),
Named {
path: Path,
args: Vec<Type>,
},
Pointer(Box<Type>),
Ref(Box<Type>),
RvalueRef(Box<Type>),
Const(Box<Type>),
Array {
element: Box<Type>,
length: Box<Expr>,
},
Void,
Auto,
}
impl Type {
#[must_use]
pub fn named(path: Path) -> Self {
Self::Named {
path,
args: Vec::new(),
}
}
#[must_use]
pub fn template(path: Path, args: Vec<Self>) -> Self {
Self::Named { path, args }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Primitive {
Bool,
Char,
Char16,
Char32,
WChar,
Int8,
Int16,
Int32,
Int64,
UInt8,
UInt16,
UInt32,
UInt64,
Float,
Double,
LongDouble,
Size,
}
impl Primitive {
pub const ALL: [Self; 17] = [
Self::Bool,
Self::Char,
Self::Char16,
Self::Char32,
Self::WChar,
Self::Int8,
Self::Int16,
Self::Int32,
Self::Int64,
Self::UInt8,
Self::UInt16,
Self::UInt32,
Self::UInt64,
Self::Float,
Self::Double,
Self::LongDouble,
Self::Size,
];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Bool => "bool",
Self::Char => "char",
Self::Char16 => "char16_t",
Self::Char32 => "char32_t",
Self::WChar => "wchar_t",
Self::Int8 => "int8_t",
Self::Int16 => "int16_t",
Self::Int32 => "int32_t",
Self::Int64 => "int64_t",
Self::UInt8 => "uint8_t",
Self::UInt16 => "uint16_t",
Self::UInt32 => "uint32_t",
Self::UInt64 => "uint64_t",
Self::Float => "float",
Self::Double => "double",
Self::LongDouble => "long double",
Self::Size => "size_t",
}
}
#[must_use]
pub const fn header(self) -> Option<&'static str> {
match self {
Self::Int8
| Self::Int16
| Self::Int32
| Self::Int64
| Self::UInt8
| Self::UInt16
| Self::UInt32
| Self::UInt64 => Some("cstdint"),
Self::Size => Some("cstddef"),
Self::Bool
| Self::Char
| Self::Char16
| Self::Char32
| Self::WChar
| Self::Float
| Self::Double
| Self::LongDouble => None,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Block {
pub statements: Vec<Statement>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Statement {
Declare {
type_: Type,
name: Ident,
value: Option<Expr>,
},
Expr(Expr),
Return(Option<Expr>),
If {
condition: Expr,
then: Block,
otherwise: Option<Block>,
},
For {
init: Box<Statement>,
condition: Expr,
step: Expr,
body: Block,
},
While {
condition: Expr,
body: Block,
},
Break,
ForEach {
binding: Ident,
range: Expr,
body: Block,
},
Switch {
value: Expr,
cases: Vec<SwitchCase>,
},
Throw(Expr),
Try {
body: Block,
handlers: Vec<CatchClause>,
},
Comment(Doc),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CatchClause {
pub type_: Option<Type>,
pub binding: Option<Ident>,
pub body: Block,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SwitchCase {
pub labels: Vec<Expr>,
pub body: Block,
pub breaks: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Expr {
Literal(Literal),
Path(Path),
Call {
callee: Box<Expr>,
args: Vec<Expr>,
},
MethodCall {
receiver: Box<Expr>,
name: Ident,
args: Vec<Expr>,
},
Member {
value: Box<Expr>,
name: Ident,
},
Arrow {
value: Box<Expr>,
name: Ident,
},
Index {
value: Box<Expr>,
index: Box<Expr>,
},
Construct {
type_: Box<Type>,
args: Vec<Expr>,
braced: bool,
},
Unary {
op: &'static str,
value: Box<Expr>,
},
Binary {
op: &'static str,
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Cast {
kind: CastKind,
type_: Box<Type>,
value: Box<Expr>,
},
AddressOf(Box<Expr>),
Deref(Box<Expr>),
InitList(Vec<Expr>),
SizeOf(Box<Type>),
Paren(Box<Expr>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CastKind {
Static,
Const,
Reinterpret,
Dynamic,
}
impl CastKind {
pub const ALL: [Self; 4] = [Self::Static, Self::Const, Self::Reinterpret, Self::Dynamic];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Static => "static_cast",
Self::Const => "const_cast",
Self::Reinterpret => "reinterpret_cast",
Self::Dynamic => "dynamic_cast",
}
}
}
#[must_use]
pub fn escape_keyword(name: &str) -> String {
if RESERVED.contains(&name) {
let mut out = name.to_string();
out.push('_');
return out;
}
name.to_string()
}
const RESERVED: &[&str] = &[
"alignas",
"alignof",
"and",
"asm",
"auto",
"bitand",
"bitor",
"bool",
"break",
"case",
"catch",
"char",
"class",
"compl",
"concept",
"const",
"consteval",
"constexpr",
"const_cast",
"continue",
"decltype",
"default",
"delete",
"do",
"double",
"dynamic_cast",
"else",
"enum",
"explicit",
"export",
"extern",
"false",
"float",
"for",
"friend",
"goto",
"if",
"inline",
"int",
"long",
"mutable",
"namespace",
"new",
"noexcept",
"not",
"nullptr",
"operator",
"or",
"private",
"protected",
"public",
"register",
"reinterpret_cast",
"requires",
"return",
"short",
"signed",
"sizeof",
"static",
"static_assert",
"static_cast",
"struct",
"switch",
"template",
"this",
"thread_local",
"throw",
"true",
"try",
"typedef",
"typeid",
"typename",
"union",
"unsigned",
"using",
"virtual",
"void",
"volatile",
"wchar_t",
"while",
"xor",
];
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use super::*;
#[test]
fn every_primitive_writes_its_own_spelling() {
let mut seen = Vec::new();
for primitive in Primitive::ALL {
let text = primitive.as_str();
assert!(!seen.contains(&text), "{text} is used twice");
seen.push(text);
}
}
#[test]
fn access_is_a_section_rather_than_a_word_on_each_member() {
let class = Class {
doc: Doc::none(),
is_struct: false,
name: Ident::new("Point"),
bases: Vec::new(),
sections: alloc::vec![
Section {
access: Access::Public,
members: Vec::new(),
},
Section {
access: Access::Private,
members: Vec::new(),
},
],
};
let order: Vec<Access> = class.sections.iter().map(|s| s.access).collect();
assert_eq!(order, alloc::vec![Access::Public, Access::Private]);
}
#[test]
fn a_keyword_escapes_with_a_trailing_underscore() {
assert_eq!(escape_keyword("class"), "class_");
assert_eq!(escape_keyword("operator"), "operator_");
assert_eq!(escape_keyword("value"), "value");
}
#[test]
fn a_defaulted_constructor_is_its_own_body_form() {
assert_ne!(
FunctionBody::Defaulted,
FunctionBody::Block(Block::default())
);
assert_ne!(FunctionBody::Deleted, FunctionBody::None);
}
}