use crate::common::{BodyText, Doc, Ident, Literal};
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 guard: Option<Ident>,
pub includes: Vec<Include>,
pub decls: Vec<Decl>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Include {
pub path: String,
pub system: bool,
}
impl Include {
#[must_use]
pub fn system(path: &str) -> Self {
Self {
path: path.to_string(),
system: true,
}
}
#[must_use]
pub fn local(path: &str) -> Self {
Self {
path: path.to_string(),
system: false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Decl {
Struct(Struct),
Union(Union),
Enum(Enum),
Typedef(Typedef),
Function(Function),
Variable(Variable),
Define(Define),
Comment(Doc),
Verbatim(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Struct {
pub doc: Doc,
pub name: Option<Ident>,
pub members: Vec<Member>,
pub body_text: BodyText,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Union {
pub doc: Doc,
pub name: Option<Ident>,
pub members: Vec<Member>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Member {
pub doc: Doc,
pub type_: Type,
pub name: Ident,
pub dims: Vec<Expr>,
pub bits: Option<u16>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Enum {
pub doc: Doc,
pub name: Option<Ident>,
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 Typedef {
pub doc: Doc,
pub type_: Type,
pub name: Ident,
pub dims: Vec<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Function {
pub doc: Doc,
pub is_static: bool,
pub is_inline: bool,
pub is_maybe_unused: bool,
pub returns: Type,
pub name: Ident,
pub params: Vec<Param>,
pub body: Option<Block>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Param {
pub type_: Type,
pub name: Option<Ident>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Variable {
pub doc: Doc,
pub is_static: bool,
pub is_const: bool,
pub type_: Type,
pub name: Ident,
pub value: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Define {
pub doc: Doc,
pub name: Ident,
pub value: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Type {
Primitive(Primitive),
Named(Ident),
StructTag(Ident),
UnionTag(Ident),
EnumTag(Ident),
Pointer(Box<Type>),
Const(Box<Type>),
Void,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Primitive {
Char,
Bool,
Int8,
Int16,
Int32,
Int64,
UInt8,
UInt16,
UInt32,
UInt64,
Float,
Double,
LongDouble,
Size,
}
impl Primitive {
pub const ALL: [Self; 14] = [
Self::Char,
Self::Bool,
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::Char => "char",
Self::Bool => "bool",
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("stdint.h"),
Self::Bool => Some("stdbool.h"),
Self::Size => Some("stddef.h"),
Self::Char | 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,
Comment(Doc),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Expr {
Literal(Literal),
Name(Ident),
Call {
callee: Box<Expr>,
args: Vec<Expr>,
},
Member {
value: Box<Expr>,
name: Ident,
},
Arrow {
value: Box<Expr>,
name: Ident,
},
Index {
value: Box<Expr>,
index: Box<Expr>,
},
Unary {
op: &'static str,
value: Box<Expr>,
},
AddressOf(Box<Expr>),
Deref(Box<Expr>),
Binary {
op: &'static str,
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Cast {
type_: Box<Type>,
value: Box<Expr>,
},
SizeOf(Box<Type>),
Paren(Box<Expr>),
Initializer(Vec<Expr>),
}
#[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] = &[
"auto",
"break",
"case",
"char",
"const",
"continue",
"default",
"do",
"double",
"else",
"enum",
"extern",
"float",
"for",
"goto",
"if",
"inline",
"int",
"long",
"register",
"restrict",
"return",
"short",
"signed",
"sizeof",
"static",
"struct",
"switch",
"typedef",
"union",
"unsigned",
"void",
"volatile",
"while",
"_Bool",
"_Complex",
"_Imaginary",
"_Alignas",
"_Alignof",
"_Atomic",
"_Generic",
"_Noreturn",
"_Static_assert",
"_Thread_local",
];
#[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 a_fixed_width_primitive_names_the_header_it_needs() {
assert_eq!(Primitive::Int32.header(), Some("stdint.h"));
assert_eq!(Primitive::Bool.header(), Some("stdbool.h"));
assert_eq!(Primitive::Double.header(), None);
}
#[test]
fn a_keyword_escapes_with_a_trailing_underscore() {
assert_eq!(escape_keyword("struct"), "struct_");
assert_eq!(escape_keyword("value"), "value");
}
#[test]
fn an_array_member_keeps_its_dimensions_in_order() {
let member = Member {
doc: Doc::none(),
type_: Type::Primitive(Primitive::Int32),
name: Ident::new("grid"),
dims: alloc::vec![
Expr::Literal(Literal::int(2)),
Expr::Literal(Literal::int(3))
],
bits: None,
};
assert_eq!(member.dims.len(), 2);
}
}