use std::any::{Any, TypeId}; use std::fmt::{self, Debug, Formatter, Write}; use std::sync::Arc; use ecow::{eco_format, EcoString, EcoVec}; use super::{Content, ElemFunc, Label, Location}; use crate::diag::{bail, StrResult}; use crate::eval::{ cast, CastInfo, Dict, FromValue, Func, IntoValue, Reflect, Regex, Value, }; use crate::model::Locatable; use crate::util::pretty_array_like; /// A selector in a show rule. #[derive(Clone, PartialEq, Hash)] pub enum Selector { /// Matches a specific type of element. /// /// If there is a dictionary, only elements with the fields from the /// dictionary match. Elem(ElemFunc, Option), /// Matches the element at the specified location. Location(Location), /// Matches elements with a specific label. Label(Label), /// Matches text elements through a regular expression. Regex(Regex), /// Matches elements with a specific capability. Can(TypeId), /// Matches if any of the subselectors match. Or(EcoVec), /// Matches if all of the subselectors match. And(EcoVec), /// Matches all matches of `selector` before `end`. Before { selector: Arc, end: Arc, inclusive: bool }, /// Matches all matches of `selector` after `start`. After { selector: Arc, start: Arc, inclusive: bool }, } impl Selector { /// Define a simple text selector. pub fn text(text: &str) -> Self { Self::Regex(Regex::new(®ex::escape(text)).unwrap()) } /// Define a simple [`Selector::Can`] selector. pub fn can() -> Self { Self::Can(TypeId::of::()) } /// Transforms this selector and an iterator of other selectors into a /// [`Selector::Or`] selector. pub fn and(self, others: impl IntoIterator) -> Self { Self::And(others.into_iter().chain(Some(self)).collect()) } /// Transforms this selector and an iterator of other selectors into a /// [`Selector::And`] selector. pub fn or(self, others: impl IntoIterator) -> Self { Self::Or(others.into_iter().chain(Some(self)).collect()) } /// Transforms this selector into a [`Selector::Before`] selector. pub fn before(self, location: impl Into, inclusive: bool) -> Self { Self::Before { selector: Arc::new(self), end: Arc::new(location.into()), inclusive, } } /// Transforms this selector into a [`Selector::After`] selector. pub fn after(self, location: impl Into, inclusive: bool) -> Self { Self::After { selector: Arc::new(self), start: Arc::new(location.into()), inclusive, } } /// Whether the selector matches for the target. pub fn matches(&self, target: &Content) -> bool { match self { Self::Elem(element, dict) => { target.func() == *element && dict .iter() .flat_map(|dict| dict.iter()) .all(|(name, value)| target.field_ref(name) == Some(value)) } Self::Label(label) => target.label() == Some(label), Self::Regex(regex) => { target.func() == item!(text_func) && item!(text_str)(target).map_or(false, |text| regex.is_match(&text)) } Self::Can(cap) => target.can_type_id(*cap), Self::Or(selectors) => selectors.iter().any(move |sel| sel.matches(target)), Self::And(selectors) => selectors.iter().all(move |sel| sel.matches(target)), Self::Location(location) => target.location() == Some(*location), // Not supported here. Self::Before { .. } | Self::After { .. } => false, } } } impl From for Selector { fn from(value: Location) -> Self { Self::Location(value) } } impl Debug for Selector { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::Elem(elem, dict) => { f.write_str(elem.name())?; if let Some(dict) = dict { f.write_str(".where")?; dict.fmt(f)?; } Ok(()) } Self::Label(label) => label.fmt(f), Self::Regex(regex) => regex.fmt(f), Self::Can(cap) => cap.fmt(f), Self::Or(selectors) | Self::And(selectors) => { f.write_str(if matches!(self, Self::Or(_)) { "or" } else { "and" })?; let pieces: Vec<_> = selectors.iter().map(|sel| eco_format!("{sel:?}")).collect(); f.write_str(&pretty_array_like(&pieces, false)) } Self::Location(loc) => loc.fmt(f), Self::Before { selector, end: split, inclusive } | Self::After { selector, start: split, inclusive } => { selector.fmt(f)?; if matches!(self, Self::Before { .. }) { f.write_str(".before(")?; } else { f.write_str(".after(")?; } split.fmt(f)?; if !*inclusive { f.write_str(", inclusive: false")?; } f.write_char(')') } } } } cast! { type Selector: "selector", func: Func => func .element() .ok_or("only element functions can be used as selectors")? .select(), label: Label => Self::Label(label), text: EcoString => Self::text(&text), regex: Regex => Self::Regex(regex), location: Location => Self::Location(location), } /// A selector that can be used with `query`. /// /// Hopefully, this is made obsolete by a more powerful query mechanism in the /// future. #[derive(Clone, PartialEq, Hash)] pub struct LocatableSelector(pub Selector); impl Reflect for LocatableSelector { fn describe() -> CastInfo { CastInfo::Union(vec![ CastInfo::Type("function"), CastInfo::Type("label"), CastInfo::Type("selector"), ]) } fn castable(value: &Value) -> bool { matches!(value.type_name(), "function" | "label" | "selector") } } impl IntoValue for LocatableSelector { fn into_value(self) -> Value { self.0.into_value() } } impl FromValue for LocatableSelector { fn from_value(value: Value) -> StrResult { fn validate(selector: &Selector) -> StrResult<()> { match selector { Selector::Elem(elem, _) => { if !elem.can::() { Err(eco_format!("{} is not locatable", elem.name()))? } } Selector::Location(_) => {} Selector::Label(_) => {} Selector::Regex(_) => bail!("text is not locatable"), Selector::Can(_) => bail!("capability is not locatable"), Selector::Or(list) | Selector::And(list) => { for selector in list { validate(selector)?; } } Selector::Before { selector, end: split, .. } | Selector::After { selector, start: split, .. } => { for selector in [selector, split] { validate(selector)?; } } } Ok(()) } if !Self::castable(&value) { return Err(Self::error(&value)); } let selector = Selector::from_value(value)?; validate(&selector)?; Ok(Self(selector)) } } /// A selector that can be used with show rules. /// /// Hopefully, this is made obsolete by a more powerful showing mechanism in the /// future. #[derive(Clone, PartialEq, Hash)] pub struct ShowableSelector(pub Selector); impl Reflect for ShowableSelector { fn describe() -> CastInfo { CastInfo::Union(vec![ CastInfo::Type("function"), CastInfo::Type("label"), CastInfo::Type("string"), CastInfo::Type("regular expression"), CastInfo::Type("symbol"), CastInfo::Type("selector"), ]) } fn castable(value: &Value) -> bool { matches!( value.type_name(), "symbol" | "string" | "label" | "function" | "regular expression" | "selector" ) } } impl IntoValue for ShowableSelector { fn into_value(self) -> Value { self.0.into_value() } } impl FromValue for ShowableSelector { fn from_value(value: Value) -> StrResult { fn validate(selector: &Selector) -> StrResult<()> { match selector { Selector::Elem(_, _) => {} Selector::Label(_) => {} Selector::Regex(_) => {} Selector::Or(_) | Selector::And(_) | Selector::Location(_) | Selector::Can(_) | Selector::Before { .. } | Selector::After { .. } => { bail!("this selector cannot be used with show") } } Ok(()) } if !Self::castable(&value) { return Err(Self::error(&value)); } let selector = Selector::from_value(value)?; validate(&selector)?; Ok(Self(selector)) } }