use crate::{Error, backend::Match};
use super::Argument;
pub enum Predicate {
Contains {
capture: u32,
values: Vec<String>,
},
Ancestor {
capture: u32,
kinds: Vec<String>,
parent: bool,
negative: bool,
},
Offset {
capture: u32,
offsets: [isize; 4],
},
}
impl Predicate {
pub fn compile(operator: &str, arguments: Vec<Argument>) -> Result<Self, Error> {
let mut arguments = arguments.into_iter();
let Some(Argument::Capture(capture)) = arguments.next() else {
return Err(Error::Query(format!("#{operator} requires a capture")));
};
let values = arguments
.map(|argument| match argument {
Argument::String(value) => Ok(value),
_ => Err(Error::Query(format!(
"#{operator} requires literal arguments"
))),
})
.collect::<Result<Vec<_>, _>>()?;
match operator {
"contains?" if !values.is_empty() => Ok(Self::Contains { capture, values }),
"has-parent?" | "not-has-parent?" | "has-ancestor?" | "not-has-ancestor?"
if !values.is_empty() =>
{
Ok(Self::Ancestor {
capture,
kinds: values,
parent: operator.ends_with("parent?"),
negative: operator.starts_with("not-"),
})
}
"offset!" if values.len() == 4 => {
let mut offsets = [0; 4];
for (offset, value) in offsets.iter_mut().zip(values) {
*offset = value
.parse()
.map_err(|_| Error::Query("invalid #offset! argument".into()))?;
}
Ok(Self::Offset { capture, offsets })
}
_ => Err(Error::Query(format!(
"unsupported or malformed predicate: #{operator}"
))),
}
}
pub fn accepts(&self, matched: &Match, source: &str) -> bool {
let capture_index = match self {
Self::Contains { capture, .. }
| Self::Ancestor { capture, .. }
| Self::Offset { capture, .. } => *capture,
};
matched
.captures
.iter()
.filter(|capture| capture.index == capture_index)
.all(|capture| {
let text = source.get(capture.node.range.clone()).unwrap_or_default();
match self {
Self::Contains { values, .. } => {
values.iter().any(|value| text.contains(value))
}
Self::Ancestor {
kinds,
parent,
negative,
..
} => {
let count = if *parent {
1
} else {
capture.node.ancestors.len()
};
let found = capture
.node
.ancestors
.iter()
.take(count)
.any(|kind| kinds.contains(kind));
found != *negative
}
Self::Offset { .. } => true,
}
})
}
pub fn apply(&self, matched: &mut Match, source: &str) -> Result<(), Error> {
let Self::Offset { capture, offsets } = self else {
return Ok(());
};
for node in matched
.captures
.iter_mut()
.filter(|item| item.index == *capture)
.map(|item| &mut item.node)
{
let start = offset(source, node.range.start, offsets[0], offsets[1]);
let end = offset(source, node.range.end, offsets[2], offsets[3]);
let range = start
.zip(end)
.map(|(start, end)| start..end)
.filter(|range| source.get(range.clone()).is_some())
.ok_or_else(|| Error::InvalidRange(node.range.clone()))?;
node.range = range;
}
Ok(())
}
}
fn offset(source: &str, byte: usize, rows: isize, columns: isize) -> Option<usize> {
let prefix = source.get(..byte)?;
let mut start = prefix.rfind('\n').map_or(0, |newline| newline + 1);
let column = (byte - start).checked_add_signed(columns)?;
for _ in 0..rows.unsigned_abs() {
start = if rows > 0 {
start + source[start..].find('\n')? + 1
} else {
let end = start.checked_sub(1)?;
source[..end].rfind('\n').map_or(0, |newline| newline + 1)
};
}
let length = source[start..].find('\n').unwrap_or(source.len() - start);
if column > length {
return None;
}
let byte = start.checked_add(column)?;
source.is_char_boundary(byte).then_some(byte)
}