use std::collections::HashMap;
use crate::model::SectionKind;
use crate::parse::unified::Section;
use crate::pattern::FragmentKind;
use crate::pattern::Pattern;
use crate::pattern::Reading;
use crate::syntax::Parsed;
use crate::syntax::SyntaxElement;
use crate::syntax::SyntaxKind;
use crate::syntax::SyntaxNode;
use crate::syntax::SyntaxToken;
use crate::text::TextRange;
#[derive(Debug, Clone)]
pub struct Match<'t> {
source: &'t str,
range: TextRange,
reading: &'t Reading,
captures: Vec<(String, Capture<'t>)>,
}
impl<'t> Match<'t> {
pub fn range(&self) -> TextRange {
self.range
}
pub fn text(&self) -> &'t str {
self.range.source_text(self.source)
}
pub fn reading(&self) -> &'t Reading {
self.reading
}
pub fn captures(&self) -> impl Iterator<Item = (&str, &Capture<'t>)> {
self.captures.iter().map(|(name, capture)| (name.as_str(), capture))
}
pub fn capture(&self, name: &str) -> Option<&Capture<'t>> {
self.captures
.iter()
.find_map(|(n, capture)| (n == name).then_some(capture))
}
}
#[derive(Debug, Clone)]
pub struct Capture<'t> {
source: &'t str,
range: TextRange,
multi: bool,
elements: Vec<CapturedElement<'t>>,
}
impl<'t> Capture<'t> {
pub fn range(&self) -> TextRange {
self.range
}
pub fn text(&self) -> &'t str {
self.range.source_text(self.source)
}
pub fn is_multi(&self) -> bool {
self.multi
}
pub fn elements(&self) -> &[CapturedElement<'t>] {
&self.elements
}
pub fn element(&self) -> Option<CapturedElement<'t>> {
match self.elements[..] {
[element] => Some(element),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum CapturedElement<'t> {
Node(&'t SyntaxNode),
Token(&'t SyntaxToken),
}
impl<'t> CapturedElement<'t> {
pub fn kind(&self) -> SyntaxKind {
match self {
Self::Node(n) => n.kind(),
Self::Token(t) => t.kind(),
}
}
pub fn range(&self) -> TextRange {
match self {
Self::Node(n) => *n.range(),
Self::Token(t) => *t.range(),
}
}
}
impl Pattern {
pub fn matches<'t>(&'t self, target: &'t Parsed) -> Vec<Match<'t>> {
let allow_document = self.readings().len() == 1 && self.readings()[0].fragment_kind() == FragmentKind::Document;
self.run(target, target.root(), allow_document)
}
pub fn matches_in<'t>(&'t self, target: &'t Parsed, anchor: &'t SyntaxNode) -> Vec<Match<'t>> {
let allow_document = std::ptr::eq(anchor, target.root());
self.run(target, anchor, allow_document)
}
fn run<'t>(&'t self, target: &'t Parsed, anchor: &'t SyntaxNode, allow_document: bool) -> Vec<Match<'t>> {
if self.style() != target.style() {
return Vec::new();
}
let plans: Vec<ReadingPlan<'_>> = self.readings().iter().map(ReadingPlan::new).collect();
let mut search = Search {
target,
anchor,
allow_document,
readings: self.readings(),
plans,
matches: Vec::new(),
};
search.visit(target.root(), false, None, None);
search.matches
}
}
struct ReadingPlan<'p> {
usable: bool,
sites: HashMap<&'p [usize], usize>,
}
impl<'p> ReadingPlan<'p> {
fn new(reading: &'p Reading) -> Self {
let mut sites: HashMap<&'p [usize], usize> = HashMap::new();
let mut usable = true;
let mut hole_parents: Vec<&[usize]> = Vec::new();
for (index, mv) in reading.metavars().iter().enumerate() {
if !mv.site().is_exact() {
usable = false; continue;
}
let path = mv.site().path();
sites.insert(path, index);
if mv.is_multi() {
let parent = &path[..path.len() - 1];
if hole_parents.contains(&parent) {
usable = false; }
hole_parents.push(parent);
}
}
ReadingPlan { usable, sites }
}
}
struct Search<'t> {
target: &'t Parsed,
anchor: &'t SyntaxNode,
allow_document: bool,
readings: &'t [Reading],
plans: Vec<ReadingPlan<'t>>,
matches: Vec<Match<'t>>,
}
impl<'t> Search<'t> {
fn visit(
&mut self,
node: &'t SyntaxNode,
inside: bool,
section_kind: Option<&SectionKind>,
parent_kind: Option<SyntaxKind>,
) {
let inside = inside || std::ptr::eq(node, self.anchor);
let own_kind: Option<SectionKind> = (node.kind() == SyntaxKind::SECTION)
.then(|| Section::cast(self.target, node).expect("SECTION node casts").kind());
let section_kind = own_kind.as_ref().or(section_kind);
if inside {
self.try_site(node, section_kind, parent_kind);
}
for child in node.children() {
if let SyntaxElement::Node(n) = child {
self.visit(n, inside, section_kind, Some(node.kind()));
}
}
}
fn try_site(&mut self, node: &'t SyntaxNode, section_kind: Option<&SectionKind>, parent_kind: Option<SyntaxKind>) {
for (reading, plan) in self.readings.iter().zip(&self.plans) {
if !plan.usable || !admits(reading, node, section_kind, parent_kind, self.allow_document) {
continue;
}
if self
.matches
.iter()
.any(|accepted| ranges_overlap(accepted.range, *node.range()))
{
continue; }
let unifier = Unifier {
reading,
plan,
psrc: reading.parsed().source(),
tsrc: self.target.source(),
bindings: Vec::new(),
};
if let Some(captures) = unifier.unify_fragment(node) {
self.matches.push(Match {
source: self.target.source(),
range: *node.range(),
reading,
captures,
});
}
}
}
}
fn admits(
reading: &Reading,
node: &SyntaxNode,
section_kind: Option<&SectionKind>,
parent_kind: Option<SyntaxKind>,
allow_document: bool,
) -> bool {
match reading.fragment_kind() {
FragmentKind::Entry => {
node.kind() == reading.fragment().kind()
&& section_kind.is_some_and(|kind| reading.section_kinds().contains(kind))
}
FragmentKind::Body => {
node.kind() == SyntaxKind::DESCRIPTION
&& parent_kind == Some(SyntaxKind::SECTION)
&& matches!(section_kind, Some(SectionKind::FreeText(_)))
}
FragmentKind::Section => node.kind() == SyntaxKind::SECTION,
FragmentKind::Document => allow_document && node.kind() == SyntaxKind::DOCUMENT,
#[allow(unreachable_patterns)]
_ => false,
}
}
fn ranges_overlap(a: TextRange, b: TextRange) -> bool {
a.start() < b.end() && b.start() < a.end()
}
fn skipped(kind: SyntaxKind) -> bool {
matches!(kind, SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE)
}
fn text_is_layout(kind: SyntaxKind) -> bool {
matches!(kind, SyntaxKind::BLANK_LINE | SyntaxKind::UNDERLINE)
}
struct Unifier<'a, 't> {
reading: &'t Reading,
plan: &'a ReadingPlan<'t>,
psrc: &'t str,
tsrc: &'t str,
bindings: Vec<(&'t str, Capture<'t>)>,
}
impl<'t> Unifier<'_, 't> {
fn unify_fragment(mut self, node: &'t SyntaxNode) -> Option<Vec<(String, Capture<'t>)>> {
let mut path: Vec<usize> = self.reading.fragment_path().to_vec();
let ok = if let Some(&mv) = self.plan.sites.get(path.as_slice()) {
let multi = self.reading.metavars()[mv].is_multi();
(multi || !node.range().is_empty())
&& self.bind(
mv,
Capture {
source: self.tsrc,
range: *node.range(),
multi,
elements: vec![CapturedElement::Node(node)],
},
)
} else {
self.reading.fragment().kind() == node.kind()
&& self.unify_children(self.reading.fragment(), node, &mut path)
};
if !ok {
return None;
}
let mut captures: Vec<(String, Capture<'t>)> = Vec::new();
for mv in self.reading.metavars() {
if !captures.iter().any(|(name, _)| name == mv.name()) {
let capture = self
.bindings
.iter()
.find_map(|(name, c)| (*name == mv.name()).then(|| c.clone()))
.expect("every exact metavariable site is visited during unification");
captures.push((mv.name().to_owned(), capture));
}
}
Some(captures)
}
fn unify_children(&mut self, pnode: &'t SyntaxNode, tnode: &'t SyntaxNode, path: &mut Vec<usize>) -> bool {
let pcs: Vec<(usize, &'t SyntaxElement)> = pnode
.children()
.iter()
.enumerate()
.filter(|(_, c)| !skipped(c.kind()))
.collect();
let tcs: Vec<&'t SyntaxElement> = tnode.children().iter().filter(|c| !skipped(c.kind())).collect();
let hole: Option<usize> = pcs.iter().position(|(index, _)| {
path.push(*index);
let is_hole = self
.plan
.sites
.get(path.as_slice())
.is_some_and(|&mv| self.reading.metavars()[mv].is_multi());
path.pop();
is_hole
});
let Some(hole) = hole else {
return pcs.len() == tcs.len()
&& pcs
.iter()
.zip(&tcs)
.all(|((index, pc), tc)| self.unify_element(*index, pc, tc, path));
};
let suffix_len = pcs.len() - hole - 1;
if tcs.len() < hole + suffix_len {
return false;
}
if !pcs[..hole]
.iter()
.zip(&tcs[..hole])
.all(|((index, pc), tc)| self.unify_element(*index, pc, tc, path))
{
return false;
}
let middle_end = tcs.len() - suffix_len;
if !pcs[hole + 1..]
.iter()
.zip(&tcs[middle_end..])
.all(|((index, pc), tc)| self.unify_element(*index, pc, tc, path))
{
return false;
}
path.push(pcs[hole].0);
let mv = self.plan.sites[path.as_slice()];
path.pop();
let middle = &tcs[hole..middle_end];
let range = match middle {
[] => {
let position = if hole > 0 {
tcs[hole - 1].range().end()
} else if middle_end < tcs.len() {
tcs[middle_end].range().start()
} else {
tnode.range().start()
};
TextRange::new(position, position)
}
[first, .., last] => TextRange::new(first.range().start(), last.range().end()),
[only] => *only.range(),
};
self.bind(
mv,
Capture {
source: self.tsrc,
range,
multi: true,
elements: middle.iter().map(|c| captured(c)).collect(),
},
)
}
fn unify_element(
&mut self,
index: usize,
pc: &'t SyntaxElement,
tc: &'t SyntaxElement,
path: &mut Vec<usize>,
) -> bool {
path.push(index);
let ok = if let Some(&mv) = self.plan.sites.get(path.as_slice()) {
tc.kind() == pc.kind()
&& !tc.range().is_empty()
&& self.bind(
mv,
Capture {
source: self.tsrc,
range: *tc.range(),
multi: false,
elements: vec![captured(tc)],
},
)
} else {
match (pc, tc) {
(SyntaxElement::Token(p), SyntaxElement::Token(t)) => {
p.kind() == t.kind() && (text_is_layout(p.kind()) || p.text(self.psrc) == t.text(self.tsrc))
}
(SyntaxElement::Node(p), SyntaxElement::Node(t)) => {
p.kind() == t.kind() && self.unify_children(p, t, path)
}
_ => false,
}
};
path.pop();
ok
}
fn bind(&mut self, mv: usize, capture: Capture<'t>) -> bool {
let name = self.reading.metavars()[mv].name();
match self.bindings.iter().find(|(n, _)| *n == name) {
Some((_, existing)) => existing.text() == capture.text(),
None => {
self.bindings.push((name, capture));
true
}
}
}
}
fn captured<'t>(element: &'t SyntaxElement) -> CapturedElement<'t> {
match element {
SyntaxElement::Node(n) => CapturedElement::Node(n),
SyntaxElement::Token(t) => CapturedElement::Token(t),
}
}