use std::collections::BTreeMap;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Prim {
Bool,
I8,
I16,
I32,
I64,
U8,
U16,
U32,
U64,
F32,
F64,
String,
Bytes,
}
impl Prim {
pub const ALL: [Prim; 13] = [
Prim::Bool,
Prim::I8,
Prim::I16,
Prim::I32,
Prim::I64,
Prim::U8,
Prim::U16,
Prim::U32,
Prim::U64,
Prim::F32,
Prim::F64,
Prim::String,
Prim::Bytes,
];
#[must_use]
pub fn from_name(name: &str) -> Option<Prim> {
Prim::ALL.into_iter().find(|p| p.snake() == name)
}
#[must_use]
pub fn snake(self) -> &'static str {
match self {
Prim::Bool => "bool",
Prim::I8 => "i8",
Prim::I16 => "i16",
Prim::I32 => "i32",
Prim::I64 => "i64",
Prim::U8 => "u8",
Prim::U16 => "u16",
Prim::U32 => "u32",
Prim::U64 => "u64",
Prim::F32 => "f32",
Prim::F64 => "f64",
Prim::String => "string",
Prim::Bytes => "bytes",
}
}
#[must_use]
pub fn pascal(self) -> &'static str {
match self {
Prim::Bool => "Bool",
Prim::I8 => "I8",
Prim::I16 => "I16",
Prim::I32 => "I32",
Prim::I64 => "I64",
Prim::U8 => "U8",
Prim::U16 => "U16",
Prim::U32 => "U32",
Prim::U64 => "U64",
Prim::F32 => "F32",
Prim::F64 => "F64",
Prim::String => "String",
Prim::Bytes => "Bytes",
}
}
#[must_use]
pub fn is_numeric(self) -> bool {
!matches!(self, Prim::Bool | Prim::String | Prim::Bytes)
}
#[must_use]
pub fn is_integer(self) -> bool {
self.is_numeric() && !matches!(self, Prim::F32 | Prim::F64)
}
}
impl fmt::Display for Prim {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.snake())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Ty {
Prim(Prim),
Record(String),
RichEnum(String),
Enum(String),
Interface(String),
CallbackInterface(String),
Optional(Box<Ty>),
List(Box<Ty>),
Map(Box<Ty>, Box<Ty>),
Iterator(Box<Ty>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Family {
Direct,
String,
Bytes,
Buffer,
Object {
nullable: bool,
},
Callback {
nullable: bool,
},
Iterator,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WireType<'a> {
Prim(Prim),
Object(&'a str),
Enum(&'a str),
User(&'a str),
Optional(&'a Ty),
List(&'a Ty),
Map(&'a Ty, &'a Ty),
}
impl Ty {
pub fn family(&self) -> Family {
match self {
Ty::Prim(Prim::String) => Family::String,
Ty::Prim(Prim::Bytes) => Family::Bytes,
Ty::Prim(_) | Ty::Enum(_) => Family::Direct,
Ty::Record(_) | Ty::RichEnum(_) | Ty::List(_) | Ty::Map(_, _) => Family::Buffer,
Ty::Interface(_) => Family::Object { nullable: false },
Ty::Optional(inner) if matches!(inner.as_ref(), Ty::Interface(_)) => {
Family::Object { nullable: true }
}
Ty::Optional(inner) if matches!(inner.as_ref(), Ty::CallbackInterface(_)) => {
Family::Callback { nullable: true }
}
Ty::Optional(_) => Family::Buffer,
Ty::CallbackInterface(_) => Family::Callback { nullable: false },
Ty::Iterator(_) => Family::Iterator,
}
}
pub fn is_buffered(&self) -> bool {
self.family() == Family::Buffer
}
pub fn user_name(&self) -> Option<&str> {
match self {
Ty::Record(n)
| Ty::RichEnum(n)
| Ty::Enum(n)
| Ty::Interface(n)
| Ty::CallbackInterface(n) => Some(n),
_ => None,
}
}
pub fn interface_name(&self) -> Option<&str> {
match self {
Ty::Interface(n) => Some(n),
Ty::Optional(inner) => inner.interface_name(),
_ => None,
}
}
pub fn callback_interface_name(&self) -> Option<&str> {
match self {
Ty::CallbackInterface(n) => Some(n),
Ty::Optional(inner) => inner.callback_interface_name(),
_ => None,
}
}
pub fn wire(&self) -> WireType<'_> {
match self {
Ty::Prim(p) => WireType::Prim(*p),
Ty::Interface(n) => WireType::Object(n),
Ty::Enum(name) => WireType::Enum(name),
Ty::Record(name) | Ty::RichEnum(name) => WireType::User(name),
Ty::Optional(inner) => WireType::Optional(inner),
Ty::List(inner) => WireType::List(inner),
Ty::Map(k, v) => WireType::Map(k, v),
Ty::CallbackInterface(_) | Ty::Iterator(_) => {
panic!("{self} cannot appear inside value buffers")
}
}
}
pub fn contains_user_type(&self) -> bool {
self.any(&|t| matches!(t, Ty::Record(_) | Ty::RichEnum(_)))
}
pub fn contains_object(&self) -> bool {
self.any(&|t| matches!(t, Ty::Interface(_)))
}
pub fn any(&self, pred: &dyn Fn(&Ty) -> bool) -> bool {
if pred(self) {
return true;
}
match self {
Ty::Optional(inner) | Ty::List(inner) | Ty::Iterator(inner) => inner.any(pred),
Ty::Map(k, v) => k.any(pred) || v.any(pred),
_ => false,
}
}
pub fn iterator_elem(&self) -> Option<&Ty> {
match self {
Ty::Iterator(inner) => Some(inner),
_ => None,
}
}
}
impl fmt::Display for Ty {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Ty::Prim(p) => f.write_str(p.snake()),
Ty::Record(n)
| Ty::RichEnum(n)
| Ty::Enum(n)
| Ty::Interface(n)
| Ty::CallbackInterface(n) => f.write_str(n),
Ty::Optional(inner) => write!(f, "{inner}?"),
Ty::List(inner) => write!(f, "[{inner}]"),
Ty::Map(k, v) => write!(f, "{{{k}:{v}}}"),
Ty::Iterator(inner) => write!(f, "iter<{inner}>"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TypeKind {
Record,
Enum,
RichEnum,
Interface,
CallbackInterface,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TypeDecl {
pub kind: TypeKind,
pub module: usize,
pub index: usize,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TypeIndex {
decls: BTreeMap<String, TypeDecl>,
module_paths: Vec<String>,
}
impl TypeIndex {
pub(crate) fn push_module(&mut self, path: String) -> usize {
self.module_paths.push(path);
self.module_paths.len() - 1
}
pub(crate) fn declare(&mut self, name: &str, decl: TypeDecl) {
self.decls.entry(name.to_string()).or_insert(decl);
}
pub fn get(&self, name: &str) -> Option<&TypeDecl> {
self.decls.get(name)
}
pub fn kind(&self, name: &str) -> Option<TypeKind> {
self.get(name).map(|d| d.kind)
}
pub fn module_path(&self, name: &str) -> Option<&str> {
self.get(name).map(|d| self.module_paths[d.module].as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
const I32: Ty = Ty::Prim(Prim::I32);
const STRING: Ty = Ty::Prim(Prim::String);
#[test]
fn families_are_total_and_agree_with_the_abi_contract() {
assert_eq!(I32.family(), Family::Direct);
assert_eq!(Ty::Prim(Prim::Bool).family(), Family::Direct);
assert_eq!(Ty::Enum("Color".into()).family(), Family::Direct);
assert_eq!(STRING.family(), Family::String);
assert_eq!(Ty::Prim(Prim::Bytes).family(), Family::Bytes);
for ty in [
Ty::Record("C".into()),
Ty::RichEnum("S".into()),
Ty::List(Box::new(I32)),
Ty::List(Box::new(Ty::Interface("Store".into()))),
Ty::Map(Box::new(STRING), Box::new(I32)),
Ty::Optional(Box::new(I32)),
Ty::Optional(Box::new(STRING)),
Ty::Optional(Box::new(Ty::Record("C".into()))),
] {
assert_eq!(ty.family(), Family::Buffer, "{ty}");
assert!(ty.is_buffered());
}
assert_eq!(
Ty::Interface("Store".into()).family(),
Family::Object { nullable: false }
);
assert_eq!(
Ty::Optional(Box::new(Ty::Interface("Store".into()))).family(),
Family::Object { nullable: true }
);
assert_eq!(
Ty::CallbackInterface("Listener".into()).family(),
Family::Callback { nullable: false }
);
assert_eq!(
Ty::Optional(Box::new(Ty::CallbackInterface("Listener".into()))).family(),
Family::Callback { nullable: true }
);
assert_eq!(Ty::Iterator(Box::new(I32)).family(), Family::Iterator);
}
#[test]
fn wire_shapes() {
for p in Prim::ALL {
assert_eq!(Ty::Prim(p).wire(), WireType::Prim(p));
}
assert_eq!(
Ty::Interface("Store".into()).wire(),
WireType::Object("Store")
);
assert_eq!(Ty::RichEnum("Shape".into()).wire(), WireType::User("Shape"));
assert_eq!(Ty::Enum("Color".into()).wire(), WireType::Enum("Color"));
let list = Ty::List(Box::new(I32));
assert_eq!(list.wire(), WireType::List(&I32));
}
#[test]
#[should_panic(expected = "cannot appear inside value buffers")]
fn callback_interfaces_have_no_wire_shape() {
Ty::CallbackInterface("Listener".into()).wire();
}
#[test]
fn containment_recurses() {
assert!(Ty::Record("C".into()).contains_user_type());
assert!(Ty::Map(
Box::new(STRING),
Box::new(Ty::Optional(Box::new(Ty::RichEnum("S".into()))))
)
.contains_user_type());
assert!(!Ty::List(Box::new(Ty::Enum("Color".into()))).contains_user_type());
assert!(Ty::List(Box::new(Ty::Interface("Store".into()))).contains_object());
assert!(!Ty::List(Box::new(Ty::Record("C".into()))).contains_object());
}
#[test]
fn display_is_the_idl_spelling() {
let ty = Ty::List(Box::new(Ty::Map(
Box::new(STRING),
Box::new(Ty::Optional(Box::new(Ty::Record("Contact".into())))),
)));
assert_eq!(ty.to_string(), "[{string:Contact?}]");
assert_eq!(
Ty::Iterator(Box::new(Ty::Interface("Store".into()))).to_string(),
"iter<Store>"
);
assert_eq!(Prim::String.snake(), "string");
assert_eq!(Prim::I32.pascal(), "I32");
}
#[test]
fn primitive_names_round_trip() {
for p in Prim::ALL {
assert_eq!(Prim::from_name(p.snake()), Some(p));
}
assert_eq!(Prim::from_name("usize"), None);
assert!(Prim::U8.is_integer() && !Prim::F64.is_integer() && !Prim::Bool.is_numeric());
}
}