use std::borrow::Cow;
use std::collections::HashMap;
use std::ops::Range;
use itertools::Itertools;
use log::{debug, trace};
use super::regex::CaptureGroup;
use crate::ranges::Ranges;
use crate::scoping::scope::Scope::{In, Out};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Scope<'viewee, T> {
In(T, Option<ScopeContext<'viewee>>),
Out(&'viewee str),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ROScope<'viewee>(pub Scope<'viewee, &'viewee str>);
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ROScopes<'viewee>(pub Vec<ROScope<'viewee>>);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RWScope<'viewee>(pub Scope<'viewee, Cow<'viewee, str>>);
#[cfg(test)] impl<'viewee> From<Scope<'viewee, &'viewee str>> for RWScope<'viewee> {
fn from(value: Scope<'viewee, &'viewee str>) -> Self {
Self(match value {
In(s, ctx) => In(Cow::Borrowed(s), ctx),
Out(s) => Out(s),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct RWScopes<'viewee>(pub Vec<RWScope<'viewee>>);
#[cfg(test)] impl<'viewee, I> From<I> for RWScopes<'viewee>
where
I: IntoIterator<Item = Scope<'viewee, &'viewee str>>,
{
fn from(value: I) -> Self {
Self(value.into_iter().map(Into::into).collect_vec())
}
}
impl ROScope<'_> {
#[must_use]
pub fn is_empty(&self) -> bool {
let s: &str = self.into();
s.is_empty()
}
}
pub type RangesWithContext<'viewee> = Vec<(Range<usize>, Option<ScopeContext<'viewee>>)>;
impl From<Ranges<usize>> for RangesWithContext<'_> {
fn from(val: Ranges<usize>) -> Self {
val.into_iter()
.map(|range| (range, Option::default()))
.collect()
}
}
impl<'viewee> ROScopes<'viewee> {
#[must_use]
pub fn from_raw_ranges(input: &'viewee str, ranges: RangesWithContext<'viewee>) -> Self {
trace!("Constructing scopes from raw ranges: {ranges:?}");
let mut scopes = Vec::with_capacity(ranges.len());
let mut last_end = 0;
for (Range { start, end }, context) in ranges.into_iter().sorted_by_key(|(r, _)| r.start) {
let range = last_end..start;
let out = &input[range.clone()];
if !out.is_empty() {
scopes.push(ROScope(Out(out)));
}
let range = start..end;
let r#in = &input[range.clone()];
if !r#in.is_empty() {
scopes.push(ROScope(In(r#in, context)));
}
last_end = end;
}
let range = last_end..input.len();
let tail = &input[range];
if !tail.is_empty() {
scopes.push(ROScope(Out(tail)));
}
debug!("Scopes: {scopes:?}");
ROScopes(scopes)
}
#[must_use]
pub fn invert(self) -> Self {
trace!("Inverting scopes: {:?}", self.0);
let scopes = self
.0
.into_iter()
.map(|s| match s {
ROScope(In(s, ..)) => ROScope(Out(s)),
ROScope(Out(s)) => ROScope(In(s, None)),
})
.collect();
trace!("Inverted scopes: {scopes:?}");
Self(scopes)
}
}
impl PartialEq<&str> for ROScopes<'_> {
fn eq(&self, other: &&str) -> bool {
let mut start = 0;
let mut end = None;
for scope in &self.0 {
let s: &str = scope.into();
end = Some(start + s.len());
let Some(substring) = other.get(start..end.unwrap()) else {
return false;
};
if substring != s {
return false;
}
start = end.unwrap();
}
end.map_or(other.is_empty(), |e| other.len() == e)
}
}
impl PartialEq<ROScopes<'_>> for &str {
fn eq(&self, other: &ROScopes<'_>) -> bool {
other == self
}
}
impl<'viewee> From<&'viewee ROScope<'viewee>> for &'viewee str {
fn from(s: &'viewee ROScope<'_>) -> Self {
match s.0 {
In(s, ..) | Out(s) => s,
}
}
}
impl<'viewee> From<ROScope<'viewee>> for RWScope<'viewee> {
fn from(s: ROScope<'viewee>) -> Self {
match s.0 {
In(s, ctx) => RWScope(In(Cow::Borrowed(s), ctx)),
Out(s) => RWScope(Out(s)),
}
}
}
impl<'viewee> From<&'viewee RWScope<'viewee>> for &'viewee str {
fn from(s: &'viewee RWScope<'_>) -> Self {
match &s.0 {
In(s, ..) => s,
Out(s) => s,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ScopeContext<'viewee> {
CaptureGroups(HashMap<CaptureGroup, &'viewee str>),
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
#[case(ROScopes(vec![ROScope(In("abc", None))]), "abc", true)]
#[case(ROScopes(vec![ROScope(In("cba", None))]), "cba", true)]
#[case(ROScopes(vec![ROScope(In("🦀", None))]), "🦀", true)]
#[case(ROScopes(vec![ROScope(In("🦀", None))]), "🤗", false)]
#[case(ROScopes(vec![ROScope(In("a", None)), ROScope(In("b", None))]), "ab", true)]
#[case(ROScopes(vec![ROScope(In("a", None)), ROScope(In("b", None)), ROScope(In("c", None))]), "abc", true)]
#[case(ROScopes(vec![ROScope(In("a", None)), ROScope(In("b", None))]), "ac", false)]
#[case(ROScopes(vec![ROScope(In("a", None)), ROScope(In("b", None))]), "a", false)]
#[case(ROScopes(vec![ROScope(In("a", None)), ROScope(In("b", None))]), "b", false)]
#[case(ROScopes(vec![ROScope(In("a", None)), ROScope(In("b", None)), ROScope(In("c", None))]), "acc", false)]
#[case(ROScopes(vec![ROScope(In("abc", None))]), "abcd", false)]
#[case(ROScopes(vec![ROScope(In("abcd", None))]), "abc", false)]
#[case(ROScopes(vec![ROScope(In("abc", None))]), "", false)]
#[case(ROScopes(vec![ROScope(In("", None))]), "abc", false)]
#[case(ROScopes(vec![ROScope(Out(""))]), "abc", false)]
#[case(ROScopes(vec![ROScope(In("", None)), ROScope(Out(""))]), "abc", false)]
#[case(ROScopes(vec![ROScope(In("", None))]), "", true)]
#[case(ROScopes(vec![ROScope(Out(""))]), "", true)]
#[case(ROScopes(vec![ROScope(In("", None)), ROScope(Out(""))]), "", true)]
#[case(ROScopes(vec![ROScope(In("a", None))]), "a", true)]
#[case(ROScopes(vec![ROScope(Out("a"))]), "a", true)]
fn test_scoped_view_str_equality(
#[case] scopes: ROScopes<'_>,
#[case] string: &str,
#[case] equal: bool,
) {
assert!((scopes == string) == equal);
}
}