use crate::common::{BodyText, Doc, Ident, Literal, Visibility};
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 imports: Vec<Ident>,
pub decls: Vec<Decl>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Decl {
Aggregate(AggregateDecl),
Enum(EnumDecl),
TypeAlias(TypeAliasDecl),
Constant(ConstDecl),
Protocol(ProtocolDecl),
Func(FuncDecl),
Comment(Doc),
Verbatim(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FuncDecl {
pub doc: Doc,
pub visibility: Visibility,
pub name: Ident,
pub params: Vec<Param>,
pub throws: bool,
pub returns: Option<Type>,
pub body: Vec<Stmt>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Aggregate {
Struct,
Class,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AggregateDecl {
pub doc: Doc,
pub visibility: Visibility,
pub kind: Aggregate,
pub name: Ident,
pub generics: Vec<Ident>,
pub conformances: Vec<Ident>,
pub properties: Vec<Property>,
pub statics: Vec<StaticLet>,
pub inits: Vec<Init>,
pub body_text: BodyText,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Property {
pub doc: Doc,
pub visibility: Visibility,
pub mutable: bool,
pub name: Ident,
pub type_: Type,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StaticLet {
pub doc: Doc,
pub visibility: Visibility,
pub name: Ident,
pub type_: Option<Type>,
pub value: Expr,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Init {
pub doc: Doc,
pub visibility: Visibility,
pub params: Vec<Param>,
pub body: Vec<Stmt>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Stmt {
AssignProperty {
property: Ident,
value: Expr,
},
Var {
name: Ident,
type_: Option<Type>,
value: Option<Expr>,
},
Assign {
place: Expr,
value: Expr,
},
Expr(Expr),
Return(Option<Expr>),
If {
condition: Expr,
then: Vec<Stmt>,
otherwise: Option<Vec<Stmt>>,
},
ForRange {
counter: Ident,
bound: Expr,
body: Vec<Stmt>,
},
While {
condition: Expr,
body: Vec<Stmt>,
},
Break,
Comment(Doc),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Param {
pub label: Ident,
pub internal_name: Option<Ident>,
pub type_: Type,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnumDecl {
pub doc: Doc,
pub visibility: Visibility,
pub name: Ident,
pub raw_type: Option<Type>,
pub cases: Vec<EnumCase>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnumCase {
pub doc: Doc,
pub name: Ident,
pub raw_value: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TypeAliasDecl {
pub doc: Doc,
pub visibility: Visibility,
pub name: Ident,
pub type_: Type,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConstDecl {
pub doc: Doc,
pub visibility: Visibility,
pub name: Ident,
pub type_: Option<Type>,
pub value: Expr,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProtocolDecl {
pub doc: Doc,
pub visibility: Visibility,
pub name: Ident,
pub methods: Vec<Method>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Method {
pub doc: Doc,
pub name: Ident,
pub params: Vec<Param>,
pub throws: bool,
pub returns: Option<Type>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Type {
Primitive(Primitive),
Named(Ident),
Generic {
base: Ident,
arguments: Vec<Type>,
},
Array(Box<Type>),
Optional(Box<Type>),
}
impl Type {
#[must_use]
pub fn named(text: &str) -> Self {
Self::Named(Ident::new(text))
}
#[must_use]
pub fn array(element: Self) -> Self {
Self::Array(Box::new(element))
}
#[must_use]
pub fn optional(inner: Self) -> Self {
Self::Optional(Box::new(inner))
}
#[must_use]
pub fn generic(base: &str, arguments: Vec<Self>) -> Self {
Self::Generic {
base: Ident::new(base),
arguments,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Primitive {
Bool,
Int8,
Int16,
Int32,
Int64,
UInt8,
UInt16,
UInt32,
UInt64,
Float,
Double,
String,
Any,
}
impl Primitive {
pub const ALL: [Self; 13] = [
Self::Bool,
Self::Int8,
Self::Int16,
Self::Int32,
Self::Int64,
Self::UInt8,
Self::UInt16,
Self::UInt32,
Self::UInt64,
Self::Float,
Self::Double,
Self::String,
Self::Any,
];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Bool => "Bool",
Self::Int8 => "Int8",
Self::Int16 => "Int16",
Self::Int32 => "Int32",
Self::Int64 => "Int64",
Self::UInt8 => "UInt8",
Self::UInt16 => "UInt16",
Self::UInt32 => "UInt32",
Self::UInt64 => "UInt64",
Self::Float => "Float",
Self::Double => "Double",
Self::String => "String",
Self::Any => "Any",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Expr {
Literal(Literal),
Name(Ident),
Binary {
op: &'static str,
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Call {
callee: Ident,
args: Vec<Argument>,
},
Paren(Box<Expr>),
Member {
value: Box<Expr>,
name: Ident,
},
Invoke {
receiver: Option<Box<Expr>>,
name: Ident,
args: Vec<Argument>,
},
Index {
value: Box<Expr>,
index: Box<Expr>,
},
Unary {
op: &'static str,
value: Box<Expr>,
},
ForceUnwrap(Box<Expr>),
ArrayLit(Vec<Expr>),
RepeatingArray {
element: Box<Type>,
repeated: Box<Expr>,
count: Box<Expr>,
},
TypeSelf(Box<Type>),
Try(Box<Expr>),
}
impl Expr {
#[must_use]
pub fn int(value: i128) -> Self {
Self::Literal(Literal::int(value))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Argument {
pub label: Option<Ident>,
pub value: Expr,
}
#[must_use]
pub fn escape_name(name: &str) -> String {
if RESERVED.contains(&name) {
let mut out = String::from("`");
out.push_str(name);
out.push('`');
return out;
}
name.to_string()
}
#[must_use]
pub fn escape_type_name(name: &str) -> String {
if STDLIB_TYPES.contains(&name) {
let mut out = name.to_string();
out.push('_');
return out;
}
escape_name(name)
}
#[must_use]
pub fn is_reserved(name: &str) -> bool {
RESERVED.contains(&name)
}
pub const RESERVED: &[&str] = &[
"associatedtype",
"borrowing",
"class",
"consuming",
"deinit",
"enum",
"extension",
"fileprivate",
"func",
"import",
"init",
"inout",
"internal",
"let",
"macro",
"nonisolated",
"open",
"operator",
"package",
"precedencegroup",
"private",
"protocol",
"public",
"rethrows",
"static",
"struct",
"subscript",
"typealias",
"var",
"break",
"case",
"catch",
"continue",
"default",
"defer",
"do",
"else",
"fallthrough",
"for",
"guard",
"if",
"in",
"repeat",
"return",
"throw",
"switch",
"where",
"while",
"Any",
"as",
"await",
"false",
"is",
"nil",
"self",
"Self",
"super",
"throws",
"true",
"try",
];
pub const STDLIB_TYPES: &[&str] = &[
"Bool",
"Int8",
"Int16",
"Int32",
"Int64",
"UInt8",
"UInt16",
"UInt32",
"UInt64",
"Float",
"Double",
"String",
"Array",
"Optional",
"OptionSet",
"RawRepresentable",
];
#[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);
}
assert_eq!(seen.len(), Primitive::ALL.len());
}
#[test]
fn no_primitive_has_an_implementation_defined_width() {
for primitive in Primitive::ALL {
assert!(
!matches!(primitive.as_str(), "Int" | "UInt"),
"{} has no fixed width",
primitive.as_str()
);
}
}
#[test]
fn no_primitive_is_float80_because_it_does_not_exist_on_arm() {
for primitive in Primitive::ALL {
assert_ne!(primitive.as_str(), "Float80");
}
}
#[test]
fn a_keyword_escapes_with_back_quotes_and_keeps_its_spelling() {
assert_eq!(escape_name("default"), "`default`");
assert_eq!(escape_name("class"), "`class`");
assert_eq!(escape_name("self"), "`self`");
assert_eq!(escape_name("payload"), "payload");
}
#[test]
fn the_escape_inverts_so_two_idl_names_stay_two_swift_names() {
assert_ne!(escape_name("default"), escape_name("default_"));
assert_ne!(escape_name("class"), escape_name("class_"));
}
#[test]
fn a_declared_type_named_after_a_standard_library_type_is_renamed() {
assert_eq!(escape_type_name("String"), "String_");
assert_eq!(escape_type_name("Int32"), "Int32_");
assert_eq!(escape_type_name("Array"), "Array_");
assert_eq!(escape_type_name("Pose"), "Pose");
}
#[test]
fn a_declared_type_named_after_a_keyword_takes_back_quotes() {
assert_eq!(escape_type_name("default"), "`default`");
assert_eq!(escape_type_name("protocol"), "`protocol`");
}
#[test]
fn the_reserved_list_has_no_duplicates() {
let mut seen: Vec<&str> = Vec::new();
for name in RESERVED {
assert!(!seen.contains(name), "{name} is listed twice");
seen.push(name);
}
}
#[test]
fn the_contextual_keywords_are_not_reserved() {
for name in [
"get", "set", "some", "Type", "Protocol", "optional", "final",
] {
assert!(!is_reserved(name), "{name} is not a reserved word");
assert_eq!(escape_name(name), name);
}
}
#[test]
fn an_array_type_nests_outermost_dimension_first() {
let type_ = Type::array(Type::array(Type::Primitive(Primitive::Int32)));
let Type::Array(outer) = &type_ else {
panic!("not an array");
};
let Type::Array(inner) = &**outer else {
panic!("the element is not an array");
};
assert_eq!(**inner, Type::Primitive(Primitive::Int32));
}
#[test]
fn an_optional_wraps_rather_than_replaces() {
let type_ = Type::optional(Type::Primitive(Primitive::UInt8));
let Type::Optional(inner) = &type_ else {
panic!("not an optional");
};
assert_eq!(**inner, Type::Primitive(Primitive::UInt8));
}
#[test]
fn a_class_and_a_struct_are_two_spellings_of_one_declaration() {
assert_ne!(Aggregate::Struct, Aggregate::Class);
}
#[test]
fn an_enum_with_no_cases_can_be_built_without_a_raw_type() {
let decl = EnumDecl {
doc: Doc::none(),
visibility: Visibility::Public,
name: Ident::new("Empty"),
raw_type: None,
cases: Vec::new(),
};
assert!(decl.raw_type.is_none());
assert!(decl.cases.is_empty());
}
#[test]
fn a_parameter_can_carry_a_second_name_for_its_body() {
let param = Param {
label: Ident::new("`self`"),
internal_name: Some(Ident::new("zdSelf")),
type_: Type::Primitive(Primitive::Int32),
};
assert_eq!(param.label.as_str(), "`self`");
assert_eq!(param.internal_name.expect("the name").as_str(), "zdSelf");
}
}