use alloc::vec::Vec;
use core::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum PatKind {
Wild,
Or,
Variant(u32),
Product,
Bool(bool),
Int { lo: i128, hi: i128 },
Char { lo: char, hi: char },
Lit(u64),
Slice { prefix: u32, rest: bool },
}
pub struct Pat {
pub(crate) kind: PatKind,
pub(crate) children: Vec<Pat>,
}
impl Pat {
fn leaf(kind: PatKind) -> Pat {
Pat {
kind,
children: Vec::new(),
}
}
#[must_use]
pub fn wild() -> Pat {
Pat::leaf(PatKind::Wild)
}
#[must_use]
pub fn variant(index: u32, fields: impl IntoIterator<Item = Pat>) -> Pat {
Pat {
kind: PatKind::Variant(index),
children: fields.into_iter().collect(),
}
}
#[must_use]
pub fn product(fields: impl IntoIterator<Item = Pat>) -> Pat {
Pat {
kind: PatKind::Product,
children: fields.into_iter().collect(),
}
}
#[must_use]
pub fn bool(value: bool) -> Pat {
Pat::leaf(PatKind::Bool(value))
}
#[must_use]
pub fn int(value: i128) -> Pat {
Pat::range(value, value)
}
#[must_use]
pub fn range(lo: i128, hi: i128) -> Pat {
Pat::leaf(PatKind::Int { lo, hi })
}
#[must_use]
pub fn char(value: char) -> Pat {
Pat::char_range(value, value)
}
#[must_use]
pub fn char_range(lo: char, hi: char) -> Pat {
Pat::leaf(PatKind::Char { lo, hi })
}
#[must_use]
pub fn lit(id: u64) -> Pat {
Pat::leaf(PatKind::Lit(id))
}
#[must_use]
pub fn or(alternatives: impl IntoIterator<Item = Pat>) -> Pat {
Pat {
kind: PatKind::Or,
children: alternatives.into_iter().collect(),
}
}
#[must_use]
pub fn slice(elements: impl IntoIterator<Item = Pat>) -> Pat {
let children: Vec<Pat> = elements.into_iter().collect();
let prefix = u32::try_from(children.len()).unwrap_or(u32::MAX);
Pat {
kind: PatKind::Slice {
prefix,
rest: false,
},
children,
}
}
#[must_use]
pub fn slice_rest(
prefix: impl IntoIterator<Item = Pat>,
suffix: impl IntoIterator<Item = Pat>,
) -> Pat {
let mut children: Vec<Pat> = prefix.into_iter().collect();
let prefix = u32::try_from(children.len()).unwrap_or(u32::MAX);
children.extend(suffix);
Pat {
kind: PatKind::Slice { prefix, rest: true },
children,
}
}
}
impl Drop for Pat {
fn drop(&mut self) {
if self.children.iter().all(|c| c.children.is_empty()) {
return;
}
let mut stack = core::mem::take(&mut self.children);
while let Some(mut pat) = stack.pop() {
stack.append(&mut pat.children);
}
}
}
impl Clone for Pat {
fn clone(&self) -> Pat {
let mut work: Vec<(&Pat, usize)> = Vec::new();
let mut done: Vec<Pat> = Vec::new();
work.push((self, 0));
while let Some(top) = work.last_mut() {
let (pat, next) = *top;
if let Some(child) = pat.children.get(next) {
top.1 += 1;
work.push((child, 0));
continue;
}
let _finished = work.pop();
let children = done.split_off(done.len().saturating_sub(pat.children.len()));
done.push(Pat {
kind: pat.kind,
children,
});
}
done.pop().unwrap_or_else(Pat::wild)
}
}
enum Print<'a> {
Pat(&'a Pat),
Text(&'static str),
}
impl fmt::Display for Pat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut stack = Vec::new();
stack.push(Print::Pat(self));
while let Some(item) = stack.pop() {
let pat = match item {
Print::Text(text) => {
f.write_str(text)?;
continue;
}
Print::Pat(pat) => pat,
};
let (open, close, sep) = match pat.kind {
PatKind::Wild => {
f.write_str("_")?;
continue;
}
PatKind::Bool(b) => {
write!(f, "{b}")?;
continue;
}
PatKind::Int { lo, hi } => {
if lo == hi {
write!(f, "{lo}")?;
} else {
write!(f, "{lo}..={hi}")?;
}
continue;
}
PatKind::Char { lo, hi } => {
if lo == hi {
write!(f, "{lo:?}")?;
} else {
write!(f, "{lo:?}..={hi:?}")?;
}
continue;
}
PatKind::Lit(id) => {
write!(f, "lit#{id}")?;
continue;
}
PatKind::Or if pat.children.is_empty() => {
f.write_str("!")?;
continue;
}
PatKind::Or => ("", "", " | "),
PatKind::Variant(index) => {
write!(f, "#{index}")?;
if pat.children.is_empty() {
continue;
}
("(", ")", ", ")
}
PatKind::Product if pat.children.len() == 1 => ("(", ",)", ", "),
PatKind::Product => ("(", ")", ", "),
PatKind::Slice { prefix, rest } => {
f.write_str("[")?;
stack.push(Print::Text("]"));
let split = (prefix as usize).min(pat.children.len());
let (pre, post) = pat.children.split_at(split);
push_list(&mut stack, post, ", ");
if rest {
match (pre.is_empty(), post.is_empty()) {
(true, true) => stack.push(Print::Text("..")),
(true, false) => stack.push(Print::Text(".., ")),
(false, true) => stack.push(Print::Text(", ..")),
(false, false) => stack.push(Print::Text(", .., ")),
}
} else if !pre.is_empty() && !post.is_empty() {
stack.push(Print::Text(", "));
}
push_list(&mut stack, pre, ", ");
continue;
}
};
f.write_str(open)?;
stack.push(Print::Text(close));
push_list(&mut stack, &pat.children, sep);
}
Ok(())
}
}
fn push_list<'a>(stack: &mut Vec<Print<'a>>, items: &'a [Pat], sep: &'static str) {
for (i, item) in items.iter().enumerate().rev() {
stack.push(Print::Pat(item));
if i > 0 {
stack.push(Print::Text(sep));
}
}
}
impl fmt::Debug for Pat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Pat({self})")
}
}
#[derive(Clone, Debug)]
pub struct Arm {
pat: Pat,
guarded: bool,
}
impl Arm {
#[must_use]
pub fn new(pat: Pat) -> Arm {
Arm {
pat,
guarded: false,
}
}
#[must_use]
pub fn guarded(pat: Pat) -> Arm {
Arm { pat, guarded: true }
}
#[must_use]
pub fn pat(&self) -> &Pat {
&self.pat
}
#[must_use]
pub fn is_guarded(&self) -> bool {
self.guarded
}
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use super::*;
fn deep(depth: usize) -> Pat {
let mut pat = Pat::wild();
for _ in 0..depth {
pat = Pat::variant(1, [pat]);
}
pat
}
#[test]
fn test_display_covers_every_kind() {
let pat = Pat::or([
Pat::variant(2, [Pat::product([Pat::bool(true), Pat::range(1, 3)])]),
Pat::product([Pat::char('a')]),
Pat::product([]),
Pat::slice_rest([Pat::lit(4)], [Pat::char_range('a', 'c')]),
Pat::slice_rest([], [Pat::int(1)]),
Pat::slice_rest([Pat::int(1)], []),
Pat::slice([]),
Pat::or([]),
]);
assert_eq!(
pat.to_string(),
"#2((true, 1..=3)) | ('a',) | () | [lit#4, .., 'a'..='c'] | [.., 1] | [1, ..] | [] | !"
);
assert_eq!(alloc::format!("{pat:?}").len(), pat.to_string().len() + 5);
}
#[test]
fn test_deep_pattern_clone_display_drop_do_not_recurse() {
let pat = deep(200_000);
let copy = pat.clone();
let text = copy.to_string();
assert_eq!(text.len(), 200_000 * 4 + 1);
drop(pat);
drop(copy);
}
#[test]
fn test_clone_preserves_shape() {
let pat = Pat::or([
Pat::variant(1, [Pat::int(1), Pat::wild()]),
Pat::slice([Pat::lit(2)]),
]);
assert_eq!(pat.clone().to_string(), pat.to_string());
}
}