mod type_ref;
pub use type_ref::TypeRef;
use core::fmt;
use super::{EnumVariant, Field, List};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Type<'a> {
Bool,
Int,
Float,
String,
ForeignObject,
Any,
Optional(TypeRef<'a>),
Array(TypeRef<'a>),
Map(TypeRef<'a>),
Custom(&'a str),
Enum(List<'a, EnumVariant<'a>>),
Object(List<'a, Field<'a>>),
}
impl<'a> Type<'a> {
pub const fn as_optional(&self) -> Option<&TypeRef<'a>> {
match self {
Type::Optional(optional) => Some(optional),
_ => None,
}
}
pub const fn as_array(&self) -> Option<&TypeRef<'a>> {
match self {
Type::Array(array) => Some(array),
_ => None,
}
}
pub const fn as_map(&self) -> Option<&TypeRef<'a>> {
match self {
Type::Map(map) => Some(map),
_ => None,
}
}
pub const fn as_custom(&self) -> Option<&'a str> {
match self {
Type::Custom(custom) => Some(custom),
_ => None,
}
}
pub const fn as_enum(&self) -> Option<&List<'a, EnumVariant<'a>>> {
match self {
Type::Enum(variants) => Some(variants),
_ => None,
}
}
pub const fn as_object(&self) -> Option<&List<'a, Field<'a>>> {
match self {
Type::Object(fields) => Some(fields),
_ => None,
}
}
}
impl<'a> fmt::Display for Type<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Type::Bool => write!(f, "bool"),
Type::Int => write!(f, "int"),
Type::Float => write!(f, "float"),
Type::String => write!(f, "string"),
Type::ForeignObject => write!(f, "object"),
Type::Any => write!(f, "any"),
Type::Optional(optional) => write!(f, "?{optional}"),
Type::Array(array) => write!(f, "[]{array}"),
Type::Map(map) => write!(f, "[string]{map}"),
Type::Custom(name) => write!(f, "{name}"),
Type::Enum(variants) => {
let has_variant_comments = variants.iter().any(|v| v.has_comments());
if has_variant_comments {
writeln!(f, "(")?;
for variant in variants.iter() {
for comment in variant.comments() {
writeln!(f, "\t{}", comment)?;
}
writeln!(f, "\t{}", variant.name())?;
}
write!(f, ")")
} else {
write!(f, "(")?;
let mut first = true;
for variant in variants.iter() {
if !first {
write!(f, ", ")?;
}
first = false;
write!(f, "{}", variant)?;
}
write!(f, ")")
}
}
Type::Object(fields) => {
write!(f, "(")?;
let mut first = true;
for field in fields.iter() {
if !first {
write!(f, ", ")?;
}
first = false;
write!(f, "{field}")?;
}
write!(f, ")")
}
}
}
}
impl<'a> PartialEq<TypeRef<'a>> for Type<'a> {
fn eq(&self, other: &TypeRef<'a>) -> bool {
self == other.inner()
}
}
#[cfg(test)]
mod tests {
use alloc::{string::ToString, vec};
use super::*;
use crate::{Comment, EnumVariant};
#[test]
fn type_names() {
use core::fmt::Write;
let mut buf = String::new();
buf.clear();
write!(buf, "{}", Type::Bool).unwrap();
assert_eq!(buf, "bool");
buf.clear();
write!(buf, "{}", Type::Int).unwrap();
assert_eq!(buf, "int");
buf.clear();
write!(buf, "{}", Type::Float).unwrap();
assert_eq!(buf, "float");
buf.clear();
write!(buf, "{}", Type::String).unwrap();
assert_eq!(buf, "string");
buf.clear();
write!(buf, "{}", Type::ForeignObject).unwrap();
assert_eq!(buf, "object");
buf.clear();
write!(buf, "{}", Type::Any).unwrap();
assert_eq!(buf, "any");
}
#[test]
fn complex_type_names() {
const INT_TYPE: Type<'static> = Type::Int;
const STRING_TYPE: Type<'static> = Type::String;
const BOOL_TYPE: Type<'static> = Type::Bool;
use core::fmt::Write;
let mut buf = String::new();
buf.clear();
write!(buf, "{}", Type::Optional(TypeRef::new(&INT_TYPE))).unwrap();
assert_eq!(buf, "?int");
buf.clear();
write!(buf, "{}", Type::Array(TypeRef::new(&STRING_TYPE))).unwrap();
assert_eq!(buf, "[]string");
buf.clear();
write!(buf, "{}", Type::Map(TypeRef::new(&BOOL_TYPE))).unwrap();
assert_eq!(buf, "[string]bool");
assert_eq!(
Type::Optional(TypeRef::new_owned(Type::Int)).to_string(),
"?int"
);
assert_eq!(
Type::Array(TypeRef::new_owned(Type::String)).to_string(),
"[]string"
);
let nested_type = Type::Array(TypeRef::new_owned(Type::Optional(TypeRef::new_owned(
Type::String,
))));
assert_eq!(nested_type.to_string(), "[]?string");
let enum_type = Type::Enum(List::from(vec![
EnumVariant::new("one", &[]),
EnumVariant::new("two", &[]),
EnumVariant::new("three", &[]),
]));
assert_eq!(enum_type.to_string(), "(one, two, three)");
let struct_type = Type::Object(List::from(vec![
Field::new("first", &Type::Int, &[]),
Field::new("second", &Type::String, &[]),
]));
assert_eq!(struct_type.to_string(), "(first: int, second: string)");
}
#[test]
fn inline_enum_formatting_with_and_without_comments() {
use core::fmt::Write;
let var1 = EnumVariant::new("red", &[]);
let var2 = EnumVariant::new("green", &[]);
let var3 = EnumVariant::new("blue", &[]);
let variants_no_comments = [&var1, &var2, &var3];
let enum_no_comments = Type::Enum(List::from(&variants_no_comments[..]));
let mut buf = String::new();
write!(buf, "{}", enum_no_comments).unwrap();
assert_eq!(buf, "(red, green, blue)");
let comment_refs = [&Comment::new("Primary color")];
let var_with_comment = EnumVariant::new("red", &comment_refs);
let var_without_comment1 = EnumVariant::new("green", &[]);
let var_without_comment2 = EnumVariant::new("blue", &[]);
let variants_with_comments = [
&var_with_comment,
&var_without_comment1,
&var_without_comment2,
];
let enum_with_comments = Type::Enum(List::from(&variants_with_comments[..]));
let mut buf = String::new();
write!(buf, "{}", enum_with_comments).unwrap();
assert_eq!(buf, "(\n\t# Primary color\n\tred\n\tgreen\n\tblue\n)");
}
}