use crate::ast::{Match, SegmentKind, Service, Version};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Binding {
Segment(String),
Path(Vec<String>),
Unbound,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Part<'p> {
Segment(&'p str),
UnknownId,
UnknownRun,
}
pub const UNKNOWN_ID: &str = "\u{0}*";
pub const UNKNOWN_RUN: &str = "\u{0}**";
#[derive(Debug, Clone)]
pub struct Covering<'a> {
pub chain: Vec<&'a Match>,
pub bindings: Vec<(String, Binding)>,
}
impl Covering<'_> {
pub fn target(&self) -> &Match {
self.chain
.last()
.expect("a covering has at least one match")
}
}
pub fn covering<'a>(version: Version, service: &'a Service, path: &[&str]) -> Vec<Covering<'a>> {
let parts: Vec<Part<'_>> = path.iter().map(|s| Part::Segment(s)).collect();
covering_parts(version, service, &parts)
}
pub fn covering_parts<'a>(
version: Version,
service: &'a Service,
path: &[Part<'_>],
) -> Vec<Covering<'a>> {
let mut found = Vec::new();
for m in &service.body.matches {
visit(
version,
m,
path,
0,
&mut Vec::new(),
&mut Vec::new(),
&mut found,
);
}
found
}
fn visit<'a>(
version: Version,
m: &'a Match,
path: &[Part<'_>],
at: usize,
chain: &mut Vec<&'a Match>,
bindings: &mut Vec<(String, Binding)>,
found: &mut Vec<Covering<'a>>,
) {
chain.push(m);
let mut covered = false;
for (end, taken) in prefixes(version, m, path, at) {
let depth = bindings.len();
bindings.extend(taken);
if end == path.len() && !covered {
covered = true;
found.push(Covering {
chain: chain.clone(),
bindings: bindings.clone(),
});
}
for inner in &m.body.matches {
visit(version, inner, path, end, chain, bindings, found);
}
bindings.truncate(depth);
}
chain.pop();
}
fn prefixes(
version: Version,
m: &Match,
path: &[Part<'_>],
at: usize,
) -> Vec<(usize, Vec<(String, Binding)>)> {
let mut out = Vec::new();
take(version, &m.path, path, at, &mut Vec::new(), &mut out);
out
}
fn take(
version: Version,
segments: &[crate::ast::Segment],
path: &[Part<'_>],
at: usize,
bound: &mut Vec<(String, Binding)>,
out: &mut Vec<(usize, Vec<(String, Binding)>)>,
) {
let Some((first, rest)) = segments.split_first() else {
out.push((at, bound.clone()));
return;
};
match &first.kind {
SegmentKind::Literal(literal) => {
if path.get(at) == Some(&Part::Segment(literal)) {
take(version, rest, path, at + 1, bound, out);
}
}
SegmentKind::Wildcard(name) => {
let binding = match path.get(at) {
Some(Part::Segment(segment)) => Binding::Segment((*segment).to_owned()),
Some(Part::UnknownId) => Binding::Unbound,
Some(Part::UnknownRun) | None => return,
};
bound.push((name.clone(), binding));
take(version, rest, path, at + 1, bound, out);
bound.pop();
}
SegmentKind::Recursive(name) => {
let fewest = match version {
Version::V1 => 1,
Version::V2 => 0,
};
for end in at + fewest..=path.len() {
let segments = path[at..end]
.iter()
.map(|part| match part {
Part::Segment(s) => (*s).to_owned(),
Part::UnknownId => UNKNOWN_ID.to_owned(),
Part::UnknownRun => UNKNOWN_RUN.to_owned(),
})
.collect();
bound.push((name.clone(), Binding::Path(segments)));
take(version, rest, path, end, bound, out);
bound.pop();
}
}
}
}