312 lines
12 KiB
Rust
312 lines
12 KiB
Rust
use std::fmt::Debug;
|
|
|
|
use mappable_rc::Mrc;
|
|
|
|
use crate::ast::{Expr, Clause};
|
|
use crate::unwrap_or;
|
|
use crate::utils::iter::box_empty;
|
|
use crate::utils::{Side, Cache, mrc_derive, mrc_try_derive, to_mrc_slice};
|
|
|
|
use super::State;
|
|
use super::split_at_max_vec::split_at_max_vec;
|
|
|
|
/// Tuple with custom cloning logic
|
|
#[derive(Debug, Eq, PartialEq, Hash)]
|
|
pub struct CacheEntry<'a>(Mrc<[Expr]>, &'a SliceMatcherDnC);
|
|
impl<'a> Clone for CacheEntry<'a> {
|
|
fn clone(&self) -> Self {
|
|
let CacheEntry(mrc, matcher) = self;
|
|
CacheEntry(Mrc::clone(mrc), matcher)
|
|
}
|
|
}
|
|
|
|
|
|
/// Matcher that applies a pattern to a slice via divide-and-conquer
|
|
///
|
|
/// Upon construction, it selects the clause of highest priority, then
|
|
/// initializes its internal state for matching that clause and delegates
|
|
/// the left and right halves of the pattern to two submatchers.
|
|
///
|
|
/// Upon matching, it uses a cache to accelerate the process of executing
|
|
/// a pattern on the entire tree.
|
|
#[derive(Clone, Eq)]
|
|
pub struct SliceMatcherDnC {
|
|
/// The entire pattern this will match
|
|
pattern: Mrc<[Expr]>,
|
|
/// The exact clause this can match
|
|
clause: Mrc<Clause>,
|
|
/// Matcher for the parts of the pattern right from us
|
|
right_subm: Option<Box<SliceMatcherDnC>>,
|
|
/// Matcher for the parts of the pattern left from us
|
|
left_subm: Option<Box<SliceMatcherDnC>>,
|
|
/// Matcher for the body of this clause if it has one.
|
|
/// Must be Some if pattern is (Auto, Lambda or S)
|
|
body_subm: Option<Box<SliceMatcherDnC>>,
|
|
/// Matcher for the type of this expression if it has one (Auto usually does)
|
|
/// Optional
|
|
typ_subm: Option<Box<SliceMatcherDnC>>,
|
|
}
|
|
|
|
impl PartialEq for SliceMatcherDnC {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.pattern == other.pattern
|
|
}
|
|
}
|
|
|
|
impl std::hash::Hash for SliceMatcherDnC {
|
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
self.pattern.hash(state);
|
|
}
|
|
}
|
|
|
|
impl SliceMatcherDnC {
|
|
/// If this is true, `clause`, `typ_subm`, `body_subm` and `clause_qual_name` are meaningless.
|
|
/// If it's false, it's also false for both side matchers.
|
|
pub fn clause_is_vectorial(&self) -> bool {
|
|
matches!(self.clause.as_ref(), Clause::Placeh{vec: Some(..), ..})
|
|
}
|
|
/// If clause is a name, the qualified name this can match
|
|
pub fn clause_qual_name(&self) -> Option<Mrc<[String]>> {
|
|
if let Clause::Name { qualified, .. } = self.clause.as_ref() {Some(Mrc::clone(qualified))} else {None}
|
|
}
|
|
/// If clause is a Placeh, the key in the state the match will be stored at
|
|
pub fn state_key(&self) -> Option<&String> {
|
|
if let Clause::Placeh { key, .. } = self.clause.as_ref() {Some(key)} else {None}
|
|
}
|
|
pub fn own_max_size(&self, total: usize) -> Option<usize> {
|
|
if !self.clause_is_vectorial() {
|
|
if total == self.len() {Some(total)} else {None}
|
|
} else {
|
|
let margin = self.min(Side::Left) + self.min(Side::Right);
|
|
if margin + self.own_min_size() <= total {Some(total - margin)} else {None}
|
|
}
|
|
}
|
|
pub fn own_min_size(&self) -> usize {
|
|
if let Clause::Placeh { vec: Some((_, nonzero)), .. } = self.clause.as_ref() {
|
|
if *nonzero {1} else {0}
|
|
} else {self.len()}
|
|
}
|
|
|
|
/// Enumerate all valid subdivisions based on the reported size constraints of self and
|
|
/// the two subranges
|
|
pub fn valid_subdivisions(&self,
|
|
range: Mrc<[Expr]>
|
|
) -> impl Iterator<Item = (Mrc<[Expr]>, Mrc<[Expr]>, Mrc<[Expr]>)> {
|
|
let own_max = unwrap_or!(self.own_max_size(range.len()); return box_empty());
|
|
let own_min = self.own_min_size();
|
|
let lmin = self.min(Side::Left);
|
|
let _lmax = self.max(Side::Left, range.len());
|
|
let rmin = self.min(Side::Right);
|
|
let _rmax = self.max(Side::Right, range.len());
|
|
let full_len = range.len();
|
|
Box::new((own_min..=own_max).rev().flat_map(move |own_len| {
|
|
let wiggle = full_len - lmin - rmin - own_len;
|
|
let range = Mrc::clone(&range);
|
|
(0..=wiggle).map(move |offset| {
|
|
let first_break = lmin + offset;
|
|
let second_break = first_break + own_len;
|
|
let left = mrc_derive(&range, |p| &p[0..first_break]);
|
|
let mid = mrc_derive(&range, |p| &p[first_break..second_break]);
|
|
let right = mrc_derive(&range, |p| &p[second_break..]);
|
|
(left, mid, right)
|
|
})
|
|
}))
|
|
}
|
|
|
|
pub fn new(pattern: Mrc<[Expr]>) -> Self {
|
|
let (clause, left_subm, right_subm) = mrc_try_derive(&pattern, |p| {
|
|
if p.len() == 1 {Some(&p[0].0)} else {None}
|
|
}).map(|e| (e, None, None))
|
|
.or_else(|| split_at_max_vec(Mrc::clone(&pattern)).map(|(left, _, right)| (
|
|
mrc_derive(&pattern, |p| &p[left.len()].0),
|
|
if !left.is_empty() {Some(Box::new(Self::new(left)))} else {None},
|
|
if !right.is_empty() {Some(Box::new(Self::new(right)))} else {None}
|
|
)))
|
|
.unwrap_or_else(|| (
|
|
mrc_derive(&pattern, |p| &p[0].0),
|
|
None,
|
|
Some(Box::new(Self::new(mrc_derive(&pattern, |p| &p[1..]))))
|
|
));
|
|
Self {
|
|
pattern, right_subm, left_subm,
|
|
clause: Mrc::clone(&clause),
|
|
body_subm: clause.body().map(|b| Box::new(Self::new(b))),
|
|
typ_subm: clause.typ().map(|t| Box::new(Self::new(t)))
|
|
}
|
|
}
|
|
|
|
/// The shortest slice this pattern can match
|
|
fn len(&self) -> usize {
|
|
if self.clause_is_vectorial() {
|
|
self.min(Side::Left) + self.min(Side::Right) + self.own_min_size()
|
|
} else {self.pattern.len()}
|
|
}
|
|
/// Pick a subpattern based on the parameter
|
|
fn side(&self, side: Side) -> Option<&SliceMatcherDnC> {
|
|
match side {
|
|
Side::Left => &self.left_subm,
|
|
Side::Right => &self.right_subm
|
|
}.as_ref().map(|b| b.as_ref())
|
|
}
|
|
/// The shortest slice the given side can match
|
|
fn min(&self, side: Side) -> usize {self.side(side).map_or(0, |right| right.len())}
|
|
/// The longest slice the given side can match
|
|
fn max(&self, side: Side, total: usize) -> usize {
|
|
self.side(side).map_or(0, |m| if m.clause_is_vectorial() {
|
|
total - self.min(side.opposite()) - self.own_min_size()
|
|
} else {m.len()})
|
|
}
|
|
/// Take the smallest possible slice from the given side
|
|
fn slice_min<'a>(&self, side: Side, range: &'a [Expr]) -> &'a [Expr] {
|
|
side.slice(self.min(side), range)
|
|
}
|
|
|
|
/// Matches the body on a range
|
|
/// # Panics
|
|
/// when called on an instance that does not have a body (not Auto, Lambda or S)
|
|
fn match_body<'a>(&'a self,
|
|
range: Mrc<[Expr]>, cache: &Cache<CacheEntry<'a>, Option<State>>
|
|
) -> Option<State> {
|
|
self.body_subm.as_ref()
|
|
.expect("Missing body matcher")
|
|
.match_range_cached(range, cache)
|
|
}
|
|
/// Matches the type and body on respective ranges
|
|
/// # Panics
|
|
/// when called on an instance that does not have a body (not Auto, Lambda or S)
|
|
fn match_parts<'a>(&'a self,
|
|
typ_range: Mrc<[Expr]>, body_range: Mrc<[Expr]>,
|
|
cache: &Cache<CacheEntry<'a>, Option<State>>
|
|
) -> Option<State> {
|
|
let typ_state = if let Some(typ) = &self.typ_subm {
|
|
typ.match_range_cached(typ_range, cache)?
|
|
} else {State::new()};
|
|
let body_state = self.match_body(body_range, cache)?;
|
|
typ_state + body_state
|
|
}
|
|
|
|
/// Match the specified side-submatcher on the specified range with the cache
|
|
/// In absence of a side-submatcher empty ranges are matched to empty state
|
|
fn apply_side_with_cache<'a>(&'a self,
|
|
side: Side, range: Mrc<[Expr]>,
|
|
cache: &Cache<CacheEntry<'a>, Option<State>>
|
|
) -> Option<State> {
|
|
match &self.side(side) {
|
|
None => {
|
|
if !range.is_empty() {None}
|
|
else {Some(State::new())}
|
|
},
|
|
Some(m) => cache.try_find(&CacheEntry(range, m)).map(|s| s.as_ref().to_owned())
|
|
}
|
|
}
|
|
|
|
fn match_range_scalar_cached<'a>(&'a self,
|
|
target: Mrc<[Expr]>,
|
|
cache: &Cache<CacheEntry<'a>, Option<State>>
|
|
) -> Option<State> {
|
|
let pos = self.min(Side::Left);
|
|
if target.len() != self.pattern.len() {return None}
|
|
let mut own_state = (
|
|
self.apply_side_with_cache(Side::Left, mrc_derive(&target, |t| &t[0..pos]), cache)?
|
|
+ self.apply_side_with_cache(Side::Right, mrc_derive(&target, |t| &t[pos+1..]), cache)
|
|
)?;
|
|
match (self.clause.as_ref(), &target.as_ref()[pos].0) {
|
|
(Clause::Literal(val), Clause::Literal(tgt)) => {
|
|
if val == tgt {Some(own_state)} else {None}
|
|
}
|
|
(Clause::Placeh{key, vec: None}, tgt_clause) => {
|
|
if let Some(real_key) = key.strip_prefix('_') {
|
|
if let Clause::Name { local: Some(value), .. } = tgt_clause {
|
|
own_state.insert_name(real_key, value)
|
|
} else {None}
|
|
} else {own_state.insert_scalar(&key, &target[pos])}
|
|
}
|
|
(Clause::S(c, _), Clause::S(c_tgt, body_range)) => {
|
|
if c != c_tgt {return None}
|
|
own_state + self.match_parts(to_mrc_slice(vec![]), Mrc::clone(body_range), cache)
|
|
}
|
|
(Clause::Name{qualified, ..}, Clause::Name{qualified: q_tgt, ..}) => {
|
|
if qualified == q_tgt {Some(own_state)} else {None}
|
|
}
|
|
(Clause::Lambda(name, _, _), Clause::Lambda(name_tgt, typ_tgt, body_tgt)) => {
|
|
// Primarily, the name works as a placeholder
|
|
if let Some(state_key) = name.strip_prefix('$') {
|
|
own_state = own_state.insert_name(state_key, name_tgt)?
|
|
} else if name != name_tgt {return None}
|
|
// ^ But if you're weird like that, it can also work as a constraint
|
|
own_state + self.match_parts(Mrc::clone(typ_tgt), Mrc::clone(body_tgt), cache)
|
|
}
|
|
(Clause::Auto(name_opt, _, _), Clause::Auto(name_range, typ_range, body_range)) => {
|
|
if let Some(name) = name_opt {
|
|
// TODO: Enforce this at construction, on a type system level
|
|
let state_key = name.strip_prefix('$')
|
|
.expect("Auto patterns may only reference, never enforce the name");
|
|
own_state = own_state.insert_name_opt(state_key, name_range.as_ref())?
|
|
}
|
|
own_state + self.match_parts(Mrc::clone(typ_range), Mrc::clone(body_range), cache)
|
|
},
|
|
_ => None
|
|
}
|
|
}
|
|
|
|
/// Match the range with a vectorial _assuming we are a vectorial_
|
|
fn match_range_vectorial_cached<'a>(&'a self,
|
|
name: &str,
|
|
target: Mrc<[Expr]>,
|
|
cache: &Cache<CacheEntry<'a>, Option<State>>
|
|
) -> Option<State> {
|
|
// Step through valid slicings based on reported size constraints in order
|
|
// from longest own section to shortest and from left to right
|
|
for (left, own, right) in self.valid_subdivisions(target) {
|
|
return Some(unwrap_or!(
|
|
self.apply_side_with_cache(Side::Left, left, cache)
|
|
.and_then(|lres| lres + self.apply_side_with_cache(Side::Right, right, cache))
|
|
.and_then(|side_res| side_res.insert_vec(name, own.as_ref()));
|
|
continue
|
|
))
|
|
}
|
|
None
|
|
}
|
|
|
|
/// Try and match the specified range
|
|
pub fn match_range_cached<'a>(&'a self,
|
|
target: Mrc<[Expr]>,
|
|
cache: &Cache<CacheEntry<'a>, Option<State>>
|
|
) -> Option<State> {
|
|
if self.pattern.is_empty() {
|
|
return if target.is_empty() {Some(State::new())} else {None}
|
|
}
|
|
if self.clause_is_vectorial() {
|
|
let key = self.state_key().expect("Vectorial implies key");
|
|
self.match_range_vectorial_cached(key, target, cache)
|
|
} else {self.match_range_scalar_cached(target, cache)}
|
|
}
|
|
|
|
pub fn get_matcher_cache<'a>()
|
|
-> Cache<'a, CacheEntry<'a>, Option<State>> {
|
|
Cache::new(
|
|
|CacheEntry(tgt, matcher), cache| {
|
|
matcher.match_range_cached(tgt, cache)
|
|
}
|
|
)
|
|
}
|
|
|
|
pub fn match_range(&self, target: Mrc<[Expr]>) -> Option<State> {
|
|
self.match_range_cached(target, &Self::get_matcher_cache())
|
|
}
|
|
}
|
|
|
|
impl Debug for SliceMatcherDnC {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("Matcher")
|
|
.field("clause", &self.clause)
|
|
.field("vectorial", &self.clause_is_vectorial())
|
|
.field("min", &self.len())
|
|
.field("left", &self.left_subm)
|
|
.field("right", &self.right_subm)
|
|
.field("lmin", &self.min(Side::Left))
|
|
.field("rmin", &self.min(Side::Right))
|
|
.finish()
|
|
}
|
|
}
|