use std::fmt;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Bound {
Is(ScalarType),
Kind(crate::CapKind),
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum TypePattern {
Scalar(ScalarType),
Collection {
ctor: CollectionCtor,
args: Vec<TypePattern>,
},
Var {
name: &'static str,
bound: Option<Bound>,
},
Function {
params: Vec<TypePattern>,
result: Box<TypePattern>,
},
Unit,
Tuple(Vec<TypePattern>),
Option(Box<TypePattern>),
Iterable { item: Box<TypePattern> },
}
pub const PIPELINE_RECEIVERS: &[CollectionCtor] = &[
CollectionCtor::Vec,
CollectionCtor::Deque,
CollectionCtor::Set,
CollectionCtor::MinHeap,
CollectionCtor::MaxHeap,
CollectionCtor::Range,
CollectionCtor::BitSet,
CollectionCtor::Map,
CollectionCtor::Counter,
];
#[must_use]
pub fn is_pipeline_receiver(concrete: &TypePattern) -> bool {
match concrete {
TypePattern::Collection { ctor, .. } => PIPELINE_RECEIVERS.contains(ctor),
TypePattern::Scalar(ScalarType::Text) => true,
_ => false,
}
}
#[must_use]
pub fn pattern_matches(catalog_pat: &TypePattern, concrete_pat: &TypePattern) -> bool {
match (catalog_pat, concrete_pat) {
(TypePattern::Var { .. }, _) => true,
(TypePattern::Iterable { .. }, concrete) => is_pipeline_receiver(concrete),
(
TypePattern::Collection { ctor: c1, args: a1 },
TypePattern::Collection { ctor: c2, args: a2 },
) => {
c1 == c2
&& a1.len() == a2.len()
&& a1.iter().zip(a2).all(|(x, y)| pattern_matches(x, y))
}
(TypePattern::Tuple(a1), TypePattern::Tuple(a2)) => {
a1.len() == a2.len() && a1.iter().zip(a2).all(|(x, y)| pattern_matches(x, y))
}
(TypePattern::Option(a), TypePattern::Option(b)) => pattern_matches(a, b),
_ => catalog_pat == concrete_pat,
}
}
impl TypePattern {
#[must_use]
pub const fn var(name: &'static str) -> TypePattern {
TypePattern::Var { name, bound: None }
}
#[must_use]
pub const fn bounded(name: &'static str, bound: Bound) -> TypePattern {
TypePattern::Var {
name,
bound: Some(bound),
}
}
#[must_use]
pub const fn is_scalar(name: &'static str, scalar: ScalarType) -> TypePattern {
TypePattern::bounded(name, Bound::Is(scalar))
}
#[must_use]
pub fn iterable(item: TypePattern) -> TypePattern {
TypePattern::Iterable {
item: Box::new(item),
}
}
#[must_use]
pub const fn of_kind(name: &'static str, kind: crate::CapKind) -> TypePattern {
TypePattern::bounded(name, Bound::Kind(kind))
}
pub(crate) fn collect_bounds(&self, into: &mut Vec<(&'static str, Bound)>) {
match self {
TypePattern::Var { name, bound } => {
if let Some(b) = bound {
into.push((name, *b));
}
}
TypePattern::Collection { args, .. } | TypePattern::Tuple(args) => {
for a in args {
a.collect_bounds(into);
}
}
TypePattern::Iterable { item } => item.collect_bounds(into),
TypePattern::Option(inner) => inner.collect_bounds(into),
TypePattern::Function { params, result } => {
for p in params {
p.collect_bounds(into);
}
result.collect_bounds(into);
}
TypePattern::Scalar(_) | TypePattern::Unit => {}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum ScalarType {
Bool,
Int,
UInt,
Float,
Byte,
Char,
Text,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum CollectionCtor {
Vec,
Deque,
Map,
Set,
Counter,
MinHeap,
MaxHeap,
BitSet,
Grid,
Range,
Seq,
}
impl CollectionCtor {
pub fn arity(self) -> usize {
match self {
CollectionCtor::Map => 2,
CollectionCtor::BitSet | CollectionCtor::Range => 0,
_ => 1,
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<CollectionCtor> {
Some(match name {
"Vec" => CollectionCtor::Vec,
"Deque" => CollectionCtor::Deque,
"Map" => CollectionCtor::Map,
"Set" => CollectionCtor::Set,
"Counter" => CollectionCtor::Counter,
"MinHeap" => CollectionCtor::MinHeap,
"MaxHeap" => CollectionCtor::MaxHeap,
"BitSet" => CollectionCtor::BitSet,
"Grid" => CollectionCtor::Grid,
"Range" => CollectionCtor::Range,
_ => return None,
})
}
pub fn name(self) -> &'static str {
match self {
CollectionCtor::Vec => "Vec",
CollectionCtor::Deque => "Deque",
CollectionCtor::Map => "Map",
CollectionCtor::Set => "Set",
CollectionCtor::Counter => "Counter",
CollectionCtor::MinHeap => "MinHeap",
CollectionCtor::MaxHeap => "MaxHeap",
CollectionCtor::BitSet => "BitSet",
CollectionCtor::Grid => "Grid",
CollectionCtor::Range => "Range",
CollectionCtor::Seq => "Seq",
}
}
}
impl ScalarType {
pub fn name(self) -> &'static str {
match self {
ScalarType::Bool => "Bool",
ScalarType::Int => "Int",
ScalarType::UInt => "UInt",
ScalarType::Float => "Float",
ScalarType::Byte => "Byte",
ScalarType::Char => "Char",
ScalarType::Text => "Text",
}
}
}
impl fmt::Display for TypePattern {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TypePattern::Scalar(s) => f.write_str(s.name()),
TypePattern::Unit => f.write_str("Unit"),
TypePattern::Tuple(els) => {
f.write_str("(")?;
for (i, e) in els.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{e}")?;
}
f.write_str(")")
}
TypePattern::Option(inner) => write!(f, "Option[{inner}]"),
TypePattern::Iterable { item } => write!(f, "Iterable[{item}]"),
TypePattern::Var { name, .. } => write!(f, "{name}"),
TypePattern::Collection { ctor, args } => {
write!(f, "{ctor:?}")?;
if !args.is_empty() {
f.write_str("[")?;
for (i, a) in args.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{a}")?;
}
f.write_str("]")?;
}
Ok(())
}
TypePattern::Function { params, result } => {
f.write_str("(")?;
for (i, p) in params.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{p}")?;
}
write!(f, ") -> {result}")
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn collection_arity_matches_design() {
assert_eq!(CollectionCtor::Vec.arity(), 1);
assert_eq!(CollectionCtor::Map.arity(), 2);
assert_eq!(CollectionCtor::Set.arity(), 1);
assert_eq!(CollectionCtor::BitSet.arity(), 0);
assert_eq!(CollectionCtor::Range.arity(), 0);
assert_eq!(CollectionCtor::Grid.arity(), 1);
}
#[test]
fn scalar_names_match_user_syntax() {
assert_eq!(ScalarType::Int.name(), "Int");
assert_eq!(ScalarType::Text.name(), "Text");
}
#[test]
fn pattern_display_matches_design_syntax() {
assert_eq!(TypePattern::Scalar(ScalarType::Int).to_string(), "Int");
assert_eq!(
TypePattern::Collection {
ctor: CollectionCtor::Vec,
args: vec![TypePattern::var("T")],
}
.to_string(),
"Vec[T]"
);
assert_eq!(
TypePattern::Collection {
ctor: CollectionCtor::Map,
args: vec![
TypePattern::Scalar(ScalarType::Text),
TypePattern::Scalar(ScalarType::Int)
],
}
.to_string(),
"Map[Text, Int]"
);
let func = TypePattern::Function {
params: vec![TypePattern::var("T")],
result: Box::new(TypePattern::var("U")),
};
assert_eq!(func.to_string(), "(T) -> U");
assert_eq!(
TypePattern::iterable(TypePattern::var("T")).to_string(),
"Iterable[T]"
);
}
fn collection(ctor: CollectionCtor, args: Vec<TypePattern>) -> TypePattern {
TypePattern::Collection { ctor, args }
}
#[test]
fn the_pipeline_walks_ten_receivers_and_not_a_grid() {
let accepted = [
collection(CollectionCtor::Vec, vec![TypePattern::var("T")]),
collection(CollectionCtor::Deque, vec![TypePattern::var("T")]),
collection(CollectionCtor::Set, vec![TypePattern::var("T")]),
collection(CollectionCtor::MinHeap, vec![TypePattern::var("T")]),
collection(CollectionCtor::MaxHeap, vec![TypePattern::var("T")]),
collection(CollectionCtor::Range, vec![]),
collection(CollectionCtor::BitSet, vec![]),
collection(
CollectionCtor::Map,
vec![TypePattern::var("K"), TypePattern::var("V")],
),
collection(CollectionCtor::Counter, vec![TypePattern::var("T")]),
TypePattern::Scalar(ScalarType::Text),
];
assert_eq!(
accepted.len(),
PIPELINE_RECEIVERS.len() + 1,
"`Text` is the tenth receiver and the only one that is not a ctor"
);
for pat in &accepted {
assert!(is_pipeline_receiver(pat), "{pat} is walked by a `for`");
}
for refused in [
collection(CollectionCtor::Grid, vec![TypePattern::var("T")]),
collection(CollectionCtor::Seq, vec![TypePattern::var("T")]),
TypePattern::Scalar(ScalarType::Int),
TypePattern::Tuple(vec![TypePattern::var("K"), TypePattern::var("V")]),
] {
assert!(!is_pipeline_receiver(&refused), "{refused} is not walked");
}
}
#[test]
fn an_iterable_row_matches_by_receiver_and_reports_by_item() {
let to_map = TypePattern::iterable(TypePattern::Tuple(vec![
TypePattern::var("K"),
TypePattern::var("V"),
]));
let set_of_int = collection(
CollectionCtor::Set,
vec![TypePattern::Scalar(ScalarType::Int)],
);
assert!(pattern_matches(&to_map, &set_of_int));
let grid = collection(
CollectionCtor::Grid,
vec![TypePattern::Scalar(ScalarType::Int)],
);
assert!(!pattern_matches(&to_map, &grid));
}
#[test]
fn a_bound_on_the_item_is_found() {
let mut bounds = Vec::new();
TypePattern::iterable(TypePattern::is_scalar("T", ScalarType::Int))
.collect_bounds(&mut bounds);
assert_eq!(bounds, vec![("T", Bound::Is(ScalarType::Int))]);
}
}