comfit_core/query/
predicate.rs1use crate::{Error, backend::Match};
4
5use super::Argument;
6
7pub enum Predicate {
9 Contains {
11 capture: u32,
13 values: Vec<String>,
15 },
16 Ancestor {
18 capture: u32,
20 kinds: Vec<String>,
22 parent: bool,
24 negative: bool,
26 },
27 Offset {
29 capture: u32,
31 offsets: [isize; 4],
33 },
34}
35
36impl Predicate {
37 pub fn compile(operator: &str, arguments: Vec<Argument>) -> Result<Self, Error> {
43 let mut arguments = arguments.into_iter();
44 let Some(Argument::Capture(capture)) = arguments.next() else {
45 return Err(Error::Query(format!("#{operator} requires a capture")));
46 };
47
48 let values = arguments
49 .map(|argument| match argument {
50 Argument::String(value) => Ok(value),
51 _ => Err(Error::Query(format!(
52 "#{operator} requires literal arguments"
53 ))),
54 })
55 .collect::<Result<Vec<_>, _>>()?;
56
57 match operator {
58 "contains?" if !values.is_empty() => Ok(Self::Contains { capture, values }),
59 "has-parent?" | "not-has-parent?" | "has-ancestor?" | "not-has-ancestor?"
60 if !values.is_empty() =>
61 {
62 Ok(Self::Ancestor {
63 capture,
64 kinds: values,
65 parent: operator.ends_with("parent?"),
66 negative: operator.starts_with("not-"),
67 })
68 }
69 "offset!" if values.len() == 4 => {
70 let mut offsets = [0; 4];
71
72 for (offset, value) in offsets.iter_mut().zip(values) {
73 *offset = value
74 .parse()
75 .map_err(|_| Error::Query("invalid #offset! argument".into()))?;
76 }
77
78 Ok(Self::Offset { capture, offsets })
79 }
80 _ => Err(Error::Query(format!(
81 "unsupported or malformed predicate: #{operator}"
82 ))),
83 }
84 }
85
86 pub fn accepts(&self, matched: &Match, source: &str) -> bool {
91 let capture_index = match self {
92 Self::Contains { capture, .. }
93 | Self::Ancestor { capture, .. }
94 | Self::Offset { capture, .. } => *capture,
95 };
96 matched
97 .captures
98 .iter()
99 .filter(|capture| capture.index == capture_index)
100 .all(|capture| {
101 let text = source.get(capture.node.range.clone()).unwrap_or_default();
102
103 match self {
104 Self::Contains { values, .. } => {
105 values.iter().any(|value| text.contains(value))
106 }
107
108 Self::Ancestor {
109 kinds,
110 parent,
111 negative,
112 ..
113 } => {
114 let count = if *parent {
115 1
116 } else {
117 capture.node.ancestors.len()
118 };
119
120 let found = capture
121 .node
122 .ancestors
123 .iter()
124 .take(count)
125 .any(|kind| kinds.contains(kind));
126 found != *negative
127 }
128
129 Self::Offset { .. } => true,
130 }
131 })
132 }
133
134 pub fn apply(&self, matched: &mut Match, source: &str) -> Result<(), Error> {
141 let Self::Offset { capture, offsets } = self else {
142 return Ok(());
143 };
144
145 for node in matched
146 .captures
147 .iter_mut()
148 .filter(|item| item.index == *capture)
149 .map(|item| &mut item.node)
150 {
151 let start = offset(source, node.range.start, offsets[0], offsets[1]);
152 let end = offset(source, node.range.end, offsets[2], offsets[3]);
153 let range = start
154 .zip(end)
155 .map(|(start, end)| start..end)
156 .filter(|range| source.get(range.clone()).is_some())
157 .ok_or_else(|| Error::InvalidRange(node.range.clone()))?;
158 node.range = range;
159 }
160
161 Ok(())
162 }
163}
164
165fn offset(source: &str, byte: usize, rows: isize, columns: isize) -> Option<usize> {
166 let prefix = source.get(..byte)?;
167 let mut start = prefix.rfind('\n').map_or(0, |newline| newline + 1);
168 let column = (byte - start).checked_add_signed(columns)?;
169
170 for _ in 0..rows.unsigned_abs() {
171 start = if rows > 0 {
172 start + source[start..].find('\n')? + 1
173 } else {
174 let end = start.checked_sub(1)?;
175 source[..end].rfind('\n').map_or(0, |newline| newline + 1)
176 };
177 }
178
179 let length = source[start..].find('\n').unwrap_or(source.len() - start);
180
181 if column > length {
182 return None;
183 }
184
185 let byte = start.checked_add(column)?;
186
187 source.is_char_boundary(byte).then_some(byte)
188}