use crate::common::{BodyText, Doc, Ident, Literal, Path, Visibility};
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct File {
pub inner_attrs: Vec<Attr>,
pub doc: Doc,
pub uses: Vec<Use>,
pub items: Vec<Item>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Use {
pub path: Path,
pub names: Vec<Ident>,
pub visibility: Visibility,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Item {
Module(Module),
Struct(Struct),
Enum(Enum),
TypeAlias(TypeAlias),
Const(Const),
Trait(Trait),
Impl(Impl),
Function(Function),
Verbatim(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Module {
pub doc: Doc,
pub attrs: Vec<Attr>,
pub visibility: Visibility,
pub name: Ident,
pub uses: Vec<Use>,
pub items: Vec<Item>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Struct {
pub doc: Doc,
pub attrs: Vec<Attr>,
pub derives: Vec<Derive>,
pub visibility: Visibility,
pub name: Ident,
pub generics: Vec<GenericParam>,
pub body: StructBody,
pub body_text: BodyText,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StructBody {
Named(Vec<Field>),
Tuple(Vec<Type>),
Unit,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Field {
pub doc: Doc,
pub attrs: Vec<Attr>,
pub visibility: Visibility,
pub name: Ident,
pub type_: Type,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Enum {
pub doc: Doc,
pub attrs: Vec<Attr>,
pub derives: Vec<Derive>,
pub visibility: Visibility,
pub name: Ident,
pub generics: Vec<GenericParam>,
pub variants: Vec<Variant>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Variant {
pub doc: Doc,
pub attrs: Vec<Attr>,
pub name: Ident,
pub body: VariantBody,
pub discriminant: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum VariantBody {
Unit,
Tuple(Vec<Type>),
Named(Vec<Field>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TypeAlias {
pub doc: Doc,
pub attrs: Vec<Attr>,
pub visibility: Visibility,
pub name: Ident,
pub generics: Vec<GenericParam>,
pub type_: Type,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Const {
pub doc: Doc,
pub attrs: Vec<Attr>,
pub visibility: Visibility,
pub name: Ident,
pub type_: Type,
pub value: Expr,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Trait {
pub doc: Doc,
pub attrs: Vec<Attr>,
pub visibility: Visibility,
pub name: Ident,
pub generics: Vec<GenericParam>,
pub supertraits: Vec<Type>,
pub items: Vec<Item>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Impl {
pub doc: Doc,
pub attrs: Vec<Attr>,
pub generics: Vec<GenericParam>,
pub trait_: Option<Type>,
pub self_type: Type,
pub items: Vec<Item>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Function {
pub doc: Doc,
pub attrs: Vec<Attr>,
pub visibility: Visibility,
pub name: Ident,
pub generics: Vec<GenericParam>,
pub params: Vec<Param>,
pub returns: Option<Type>,
pub body: Option<Block>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Param {
pub name: Option<Ident>,
pub type_: Type,
pub receiver: Option<Receiver>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Receiver {
Value,
Ref,
RefMut,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GenericParam {
pub name: Ident,
pub bounds: Vec<Type>,
pub lifetime: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Type {
Named {
path: Path,
args: Vec<Type>,
},
Ref {
target: Box<Type>,
mutable: bool,
lifetime: Option<Ident>,
},
Array {
element: Box<Type>,
length: Box<Expr>,
},
Slice(Box<Type>),
Tuple(Vec<Type>),
Primitive(Primitive),
ImplTrait(Vec<Type>),
DynTrait(Vec<Type>),
}
impl Type {
#[must_use]
pub fn named(path: Path) -> Self {
Self::Named {
path,
args: Vec::new(),
}
}
#[must_use]
pub fn generic(path: Path, args: Vec<Self>) -> Self {
Self::Named { path, args }
}
#[must_use]
pub fn unit() -> Self {
Self::Tuple(Vec::new())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Primitive {
Bool,
Char,
I8,
I16,
I32,
I64,
I128,
U8,
U16,
U32,
U64,
U128,
F32,
F64,
Str,
String,
}
impl Primitive {
pub const ALL: [Self; 16] = [
Self::Bool,
Self::Char,
Self::I8,
Self::I16,
Self::I32,
Self::I64,
Self::I128,
Self::U8,
Self::U16,
Self::U32,
Self::U64,
Self::U128,
Self::F32,
Self::F64,
Self::Str,
Self::String,
];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Bool => "bool",
Self::Char => "char",
Self::I8 => "i8",
Self::I16 => "i16",
Self::I32 => "i32",
Self::I64 => "i64",
Self::I128 => "i128",
Self::U8 => "u8",
Self::U16 => "u16",
Self::U32 => "u32",
Self::U64 => "u64",
Self::U128 => "u128",
Self::F32 => "f32",
Self::F64 => "f64",
Self::Str => "str",
Self::String => "String",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Attr {
pub path: Path,
pub args: Vec<AttrArg>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AttrArg {
Word(Ident),
Pair {
name: Ident,
value: Literal,
},
Nested {
name: Ident,
args: Vec<AttrArg>,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Derive {
pub path: Path,
}
impl Derive {
#[must_use]
pub fn of(name: &str) -> Self {
Self {
path: Path::single(name),
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Block {
pub statements: Vec<Statement>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Statement {
Let {
name: Ident,
type_: Option<Type>,
value: Option<Expr>,
mutable: bool,
},
Semi(Expr),
Tail(Expr),
Item(Box<Item>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Expr {
Literal(Literal),
Path(Path),
MethodCall {
receiver: Box<Expr>,
method: Ident,
args: Vec<Expr>,
},
Call {
callee: Box<Expr>,
args: Vec<Expr>,
},
StructLit {
path: Path,
fields: Vec<(Ident, Expr)>,
base: Option<Box<Expr>>,
},
Field {
receiver: Box<Expr>,
name: Ident,
},
Ref {
target: Box<Expr>,
mutable: bool,
},
Try(Box<Expr>),
Closure {
params: Vec<Ident>,
body: Box<Expr>,
},
Match {
scrutinee: Box<Expr>,
arms: Vec<MatchArm>,
},
If {
condition: Box<Expr>,
then: Block,
otherwise: Option<Block>,
},
For {
binding: Ident,
iterator: Box<Expr>,
body: Block,
},
While {
condition: Box<Expr>,
body: Block,
},
Assign {
target: Box<Expr>,
value: Box<Expr>,
},
Return(Option<Box<Expr>>),
Array(Vec<Expr>),
Repeat {
value: Box<Expr>,
count: Box<Expr>,
},
Tuple(Vec<Expr>),
Binary {
op: BinaryOp,
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Not(Box<Expr>),
Deref(Box<Expr>),
Cast {
value: Box<Expr>,
type_: Box<Type>,
},
Block(Block),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BinaryOp {
Add,
Sub,
Mul,
Div,
Rem,
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
And,
Or,
BitAnd,
BitOr,
BitXor,
Shl,
Shr,
}
impl BinaryOp {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Add => "+",
Self::Sub => "-",
Self::Mul => "*",
Self::Div => "/",
Self::Rem => "%",
Self::Eq => "==",
Self::Ne => "!=",
Self::Lt => "<",
Self::Le => "<=",
Self::Gt => ">",
Self::Ge => ">=",
Self::And => "&&",
Self::Or => "||",
Self::BitAnd => "&",
Self::BitOr => "|",
Self::BitXor => "^",
Self::Shl => "<<",
Self::Shr => ">>",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MatchArm {
pub pattern: Pattern,
pub guard: Option<Expr>,
pub body: Expr,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Pattern {
Wildcard,
Binding(Ident),
Literal(Literal),
Path(Path),
TupleStruct {
path: Path,
elements: Vec<Pattern>,
},
Struct {
path: Path,
fields: Vec<(Ident, Pattern)>,
rest: bool,
},
}
#[must_use]
pub fn escape_keyword(name: &str) -> String {
if RESERVED.contains(&name) {
let mut out = String::from("r#");
out.push_str(name);
return out;
}
name.to_string()
}
const RESERVED: &[&str] = &[
"as", "break", "const", "continue", "else", "enum", "extern", "false", "fn", "for", "if",
"impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref", "return", "static",
"struct", "trait", "true", "type", "unsafe", "use", "where", "while", "async", "await", "dyn",
"abstract", "become", "box", "do", "final", "macro", "override", "priv", "typeof", "unsized",
"virtual", "yield", "try",
];
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use super::*;
#[test]
fn a_keyword_escapes_to_the_raw_form_and_keeps_the_name() {
assert_eq!(escape_keyword("type"), "r#type");
assert_eq!(escape_keyword("match"), "r#match");
assert_eq!(escape_keyword("value"), "value");
}
#[test]
fn self_and_crate_are_not_escapable_and_are_not_in_the_table() {
for name in ["self", "Self", "super", "crate"] {
assert!(!RESERVED.contains(&name), "{name} is in the table");
}
}
#[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 derives_keep_the_order_they_were_added_in() {
let s = Struct {
body_text: crate::common::BodyText::none(),
doc: Doc::none(),
attrs: Vec::new(),
derives: alloc::vec![Derive::of("Debug"), Derive::of("Clone")],
visibility: Visibility::Public,
name: Ident::new("Pose"),
generics: Vec::new(),
body: StructBody::Named(Vec::new()),
};
let names: Vec<&str> = s
.derives
.iter()
.filter_map(|d| d.path.last().map(Ident::as_str))
.collect();
assert_eq!(names, alloc::vec!["Debug", "Clone"]);
}
#[test]
fn an_array_length_is_an_expression_not_a_number() {
let type_ = Type::Array {
element: alloc::boxed::Box::new(Type::Primitive(Primitive::U8)),
length: alloc::boxed::Box::new(Expr::Path(Path::single("MAX"))),
};
let Type::Array { length, .. } = type_ else {
panic!("expected an array");
};
assert!(matches!(*length, Expr::Path(_)));
}
}