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 package: Option<Path>,
pub imports: Vec<Import>,
pub types: Vec<TypeDecl>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Import {
pub path: Path,
pub static_: bool,
pub wildcard: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TypeDecl {
Class(Class),
Interface(Interface),
Enum(Enum),
Record(Record),
AnnotationType(AnnotationType),
Verbatim(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Class {
pub doc: Doc,
pub annotations: Vec<Annotation>,
pub modifiers: Vec<Modifier>,
pub name: Ident,
pub generics: Vec<TypeParam>,
pub extends: Option<Type>,
pub implements: Vec<Type>,
pub members: Vec<Member>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Interface {
pub doc: Doc,
pub annotations: Vec<Annotation>,
pub modifiers: Vec<Modifier>,
pub name: Ident,
pub generics: Vec<TypeParam>,
pub extends: Vec<Type>,
pub members: Vec<Member>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Enum {
pub doc: Doc,
pub annotations: Vec<Annotation>,
pub modifiers: Vec<Modifier>,
pub name: Ident,
pub implements: Vec<Type>,
pub constants: Vec<EnumConstant>,
pub members: Vec<Member>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnumConstant {
pub doc: Doc,
pub annotations: Vec<Annotation>,
pub name: Ident,
pub args: Vec<Expr>,
pub body: Vec<Member>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Record {
pub doc: Doc,
pub annotations: Vec<Annotation>,
pub modifiers: Vec<Modifier>,
pub name: Ident,
pub generics: Vec<TypeParam>,
pub components: Vec<Param>,
pub implements: Vec<Type>,
pub members: Vec<Member>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AnnotationType {
pub doc: Doc,
pub annotations: Vec<Annotation>,
pub modifiers: Vec<Modifier>,
pub name: Ident,
pub elements: Vec<AnnotationElement>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AnnotationElement {
pub doc: Doc,
pub type_: Type,
pub name: Ident,
pub default: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Member {
Field(Field),
Method(Method),
Constructor(Constructor),
Nested(Box<TypeDecl>),
Initialiser {
static_: bool,
body: Block,
},
Comment(Doc),
Verbatim(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Field {
pub doc: Doc,
pub annotations: Vec<Annotation>,
pub modifiers: Vec<Modifier>,
pub type_: Type,
pub name: Ident,
pub value: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Method {
pub doc: Doc,
pub annotations: Vec<Annotation>,
pub modifiers: Vec<Modifier>,
pub generics: Vec<TypeParam>,
pub returns: Type,
pub name: Ident,
pub params: Vec<Param>,
pub throws: Vec<Type>,
pub body: Option<Block>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Constructor {
pub doc: Doc,
pub annotations: Vec<Annotation>,
pub modifiers: Vec<Modifier>,
pub name: Ident,
pub params: Vec<Param>,
pub throws: Vec<Type>,
pub body: Block,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Param {
pub annotations: Vec<Annotation>,
pub final_: bool,
pub type_: Type,
pub name: Ident,
pub varargs: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Modifier {
Public,
Protected,
Private,
Static,
Final,
Abstract,
Synchronized,
Transient,
Volatile,
Native,
Strictfp,
Default,
Sealed,
}
impl Modifier {
pub const ALL: [Self; 13] = [
Self::Public,
Self::Protected,
Self::Private,
Self::Static,
Self::Final,
Self::Abstract,
Self::Synchronized,
Self::Transient,
Self::Volatile,
Self::Native,
Self::Strictfp,
Self::Default,
Self::Sealed,
];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Public => "public",
Self::Protected => "protected",
Self::Private => "private",
Self::Static => "static",
Self::Final => "final",
Self::Abstract => "abstract",
Self::Synchronized => "synchronized",
Self::Transient => "transient",
Self::Volatile => "volatile",
Self::Native => "native",
Self::Strictfp => "strictfp",
Self::Default => "default",
Self::Sealed => "sealed",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Annotation {
pub path: Path,
pub args: Vec<AnnotationArg>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AnnotationArg {
Value(Expr),
Pair {
name: Ident,
value: Expr,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TypeParam {
pub name: Ident,
pub bounds: Vec<Type>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Type {
Primitive(Primitive),
Named {
path: Path,
args: Vec<Type>,
},
Array(Box<Type>),
Wildcard {
extends: Option<Box<Type>>,
super_: Option<Box<Type>>,
},
Void,
Var,
}
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 }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Primitive {
Boolean,
Byte,
Char,
Short,
Int,
Long,
Float,
Double,
}
impl Primitive {
pub const ALL: [Self; 8] = [
Self::Boolean,
Self::Byte,
Self::Char,
Self::Short,
Self::Int,
Self::Long,
Self::Float,
Self::Double,
];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Boolean => "boolean",
Self::Byte => "byte",
Self::Char => "char",
Self::Short => "short",
Self::Int => "int",
Self::Long => "long",
Self::Float => "float",
Self::Double => "double",
}
}
#[must_use]
pub const fn boxed(self) -> &'static str {
match self {
Self::Boolean => "Boolean",
Self::Byte => "Byte",
Self::Char => "Character",
Self::Short => "Short",
Self::Int => "Integer",
Self::Long => "Long",
Self::Float => "Float",
Self::Double => "Double",
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Block {
pub statements: Vec<Statement>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Statement {
Declare {
final_: bool,
type_: Type,
name: Ident,
value: Option<Expr>,
},
Expr(Expr),
Return(Option<Expr>),
If {
condition: Expr,
then: Block,
otherwise: Option<Block>,
},
ForEach {
type_: Type,
binding: Ident,
range: Expr,
body: Block,
},
For {
init: Box<Statement>,
condition: Expr,
step: Expr,
body: Block,
},
While {
condition: Expr,
body: Block,
},
Break,
Switch {
value: Expr,
cases: Vec<SwitchCase>,
},
Throw(Expr),
Comment(Doc),
}
#[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),
MethodCall {
receiver: Option<Box<Expr>>,
name: Ident,
args: Vec<Expr>,
},
Field {
value: Box<Expr>,
name: Ident,
},
Index {
value: Box<Expr>,
index: Box<Expr>,
},
New {
type_: Box<Type>,
args: Vec<Expr>,
},
NewArray {
element: Box<Type>,
lengths: Vec<Expr>,
elements: Vec<Expr>,
},
Binary {
op: &'static str,
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Paren(Box<Expr>),
Cast {
type_: Box<Type>,
value: Box<Expr>,
},
InstanceOf {
value: Box<Expr>,
type_: Box<Type>,
},
Ternary {
condition: Box<Expr>,
then: Box<Expr>,
otherwise: Box<Expr>,
},
This,
Lambda {
params: Vec<Ident>,
body: Box<Expr>,
},
Annotation(Annotation),
ArrayInit(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] = &[
"abstract",
"assert",
"boolean",
"break",
"byte",
"case",
"catch",
"char",
"class",
"const",
"continue",
"default",
"do",
"double",
"else",
"enum",
"extends",
"final",
"finally",
"float",
"for",
"goto",
"if",
"implements",
"import",
"instanceof",
"int",
"interface",
"long",
"native",
"new",
"package",
"private",
"protected",
"public",
"return",
"short",
"static",
"strictfp",
"super",
"switch",
"synchronized",
"this",
"throw",
"throws",
"transient",
"try",
"void",
"volatile",
"while",
"true",
"false",
"null",
];
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use super::*;
#[test]
fn every_primitive_writes_its_own_spelling_and_has_a_wrapper() {
let mut seen = Vec::new();
let mut boxed = Vec::new();
for primitive in Primitive::ALL {
assert!(!seen.contains(&primitive.as_str()));
seen.push(primitive.as_str());
assert!(!boxed.contains(&primitive.boxed()));
boxed.push(primitive.boxed());
}
}
#[test]
fn char_boxes_to_character_not_char() {
assert_eq!(Primitive::Char.boxed(), "Character");
assert_eq!(Primitive::Int.boxed(), "Integer");
}
#[test]
fn a_keyword_escapes_with_a_trailing_underscore() {
assert_eq!(escape_keyword("class"), "class_");
assert_eq!(escape_keyword("null"), "null_");
assert_eq!(escape_keyword("value"), "value");
}
#[test]
fn modifiers_keep_the_order_they_were_written_in() {
let field = Field {
doc: Doc::none(),
annotations: Vec::new(),
modifiers: alloc::vec![Modifier::Public, Modifier::Static, Modifier::Final],
type_: Type::Primitive(Primitive::Int),
name: Ident::new("MAX"),
value: None,
};
let written: Vec<&str> = field.modifiers.iter().map(|m| m.as_str()).collect();
assert_eq!(written, alloc::vec!["public", "static", "final"]);
}
}