1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
//! Queries: a query field's pattern compiled with the product's limits, as a stored query
//! answered from the index revision or a live text query, which reads the files as they
//! are now. Patterns are a plain identifier, or cscope's bounded C-locale regular
//! expressions (`pattern`).
pub(crate) mod pattern;
use crate::engine::{self, Context, Field, Reads, Semantics, Source};
use pattern::{CompiledPattern, PatternError, PatternLimits};
/// The product's limits on a pattern: 4,096 bytes, as a session request's length allows.
pub(crate) const LIMITS: PatternLimits = PatternLimits {
input_bytes: 4096,
group_depth: 64,
compiled_bytes: 1024 * 1024,
dfa_cache_bytes: 2 * 1024 * 1024,
};
/// A query ready to answer.
pub(crate) enum Query<'p> {
/// A stored query: fields 0 to 3, 7, 8 and 9.
Stored(StoredQuery<'p>),
/// A live text query: fields 4, 5 and 6.
Text(TextQuery<'p>),
}
/// Why a query cannot be answered at all. The wording, and any limit of a transport, are
/// the transport's.
#[derive(Debug)]
pub(crate) enum Refusal {
/// The pattern holds a NUL byte.
Nul,
/// The field is not one of cscope's.
NoField,
/// The pattern cannot be compiled for its field.
Pattern(PatternError),
}
impl<'p> Query<'p> {
/// Compiles `pattern` for query field `field` (0 to 9). A NUL byte is refused before the
/// field is looked at.
pub(crate) fn compile(
field: u8,
pattern: &'p [u8],
case: IdentifierCase,
) -> Result<Self, Refusal> {
if pattern.contains(&0) {
return Err(Refusal::Nul);
}
Ok(match field {
// In line mode cscope answers field 5, "change this text", as a text search.
4 | 5 => Self::Text(TextQuery(
CompiledPattern::compile_text(pattern, case, LIMITS).map_err(Refusal::Pattern)?,
)),
6 => Self::Text(TextQuery(
CompiledPattern::compile_regex_text(pattern, case, LIMITS)
.map_err(Refusal::Pattern)?,
)),
_ => {
let field = Field::from_digit(b'0' + field).ok_or(Refusal::NoField)?;
Self::Stored(StoredQuery::compile(field, pattern, case)?)
}
})
}
}
/// A stored query: a field answered from the Analysis units.
pub(crate) struct StoredQuery<'p> {
field: Field,
request: CompiledPattern<'p>,
}
impl<'p> StoredQuery<'p> {
/// Compiles `pattern` for the stored field `field`. A pattern holding a NUL is
/// refused before, by [`Query::compile`].
pub(crate) fn compile(
field: Field,
pattern: &'p [u8],
case: IdentifierCase,
) -> Result<Self, Refusal> {
let request = match field {
// Paths print raw, so their patterns may hold any byte but NUL, a line break or a
// carriage return, as cscope's do; includes share the file-name grammar.
Field::Files | Field::Includers => CompiledPattern::compile_path(pattern, case, LIMITS),
_ => CompiledPattern::compile(pattern, case, LIMITS),
}
.map_err(Refusal::Pattern)?;
Ok(Self { field, request })
}
pub(crate) fn field(&self) -> Field {
self.field
}
/// The pattern as field 1 shows it in each row's context column, as cscope shows it:
/// trailing blanks trimmed and, in a regular expression matched with case, its `^` and
/// `$` anchors dropped (`^Alph.$` shows `Alph.`, with `-C` `^Alph.$`).
pub(crate) fn shown_pattern(&self) -> &[u8] {
self.request.context()
}
/// What the query reads with `semantics`: a source need give it no other file.
pub(crate) fn reads(&self, semantics: Semantics) -> Reads<'_> {
engine::reads(semantics, self.field, &self.request)
}
/// The query's Result rows from `source` with `semantics`, in cscope's order.
pub(crate) fn answer<'a>(
&'a self,
semantics: Semantics,
source: &'a impl Source,
) -> impl Iterator<Item = ResultRow<'a>> + 'a {
engine::answer(semantics, self.field, &self.request, source)
.into_iter()
.map(move |row| ResultRow {
at_file_scope: row.at_file_scope(),
path: row.path,
context: match row.context {
Context::Global => b"<global>",
Context::Name(name) => name,
Context::Pattern => self.request.context(),
Context::Unknown => engine::UNKNOWN,
},
line: row.line,
text: row.text,
})
}
}
/// A live text query: a line pattern for the files as they are now.
pub(crate) struct TextQuery<'p>(CompiledPattern<'p>);
impl TextQuery<'_> {
/// The lines of `content`, each with its line end, that match, in order, with their
/// numbers from 1.
pub(crate) fn matching_lines<E>(
&self,
content: &[u8],
scratch: &mut Vec<u8>,
folded: &mut Vec<u8>,
each: impl FnMut(usize, &[u8]) -> Result<(), E>,
) -> Result<(), E> {
self.0.matching_lines(content, scratch, folded, each)
}
}
/// One answer of a stored query, before any transport writes it.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct ResultRow<'a> {
/// Whether the row is at the file scope (`<global>`): an answer gives those rows
/// first, each group in file order.
pub at_file_scope: bool,
/// The indexed path, as displayed.
pub path: &'a [u8],
/// The context column: `<global>`, the enclosing function or macro (for field 2 the
/// function called), field 1's pattern, or field 7's `<unknown>`.
pub context: &'a [u8],
pub line: u32,
/// The displayed text, before any transport escaping.
pub text: &'a [u8],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum IdentifierCase {
Sensitive,
AsciiInsensitive,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum IdentifierRequestError {
Empty,
OutsideIdentifierSubset,
}
/// A pattern that is a plain identifier (its blanks already trimmed): matched as a whole
/// name.
#[derive(Debug)]
pub(crate) struct IdentifierPattern<'request> {
context: &'request [u8],
case: IdentifierCase,
}
impl<'request> IdentifierPattern<'request> {
pub fn new(name: &'request [u8], case: IdentifierCase) -> Result<Self, IdentifierRequestError> {
let Some(first) = name.first() else {
return Err(IdentifierRequestError::Empty);
};
if !(first.is_ascii_alphabetic() || *first == b'_')
|| !name
.iter()
.all(|byte| byte.is_ascii_alphanumeric() || *byte == b'_')
{
return Err(IdentifierRequestError::OutsideIdentifierSubset);
}
Ok(Self {
context: name,
case,
})
}
pub(crate) fn matches_bytes(&self, name: &[u8]) -> bool {
match self.case {
IdentifierCase::Sensitive => name == self.context,
IdentifierCase::AsciiInsensitive => name.eq_ignore_ascii_case(self.context),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identifier_requests_take_only_identifiers() {
let request =
IdentifierPattern::new(b"alpha", IdentifierCase::Sensitive).expect("identifier");
assert!(request.matches_bytes(b"alpha"));
assert!(!request.matches_bytes(b"Alpha"));
let caseless =
IdentifierPattern::new(b"alpha", IdentifierCase::AsciiInsensitive).expect("identifier");
assert!(caseless.matches_bytes(b"ALPHA"));
for (pattern, error) in [
(&b""[..], IdentifierRequestError::Empty),
(b"alpha ", IdentifierRequestError::OutsideIdentifierSubset),
(b"9lives", IdentifierRequestError::OutsideIdentifierSubset),
(b"a-b", IdentifierRequestError::OutsideIdentifierSubset),
] {
assert_eq!(
IdentifierPattern::new(pattern, IdentifierCase::Sensitive).unwrap_err(),
error,
"{pattern:?}"
);
}
}
/// A pattern holding a NUL is refused whatever its field, before the field is looked
/// at: even field 10, which there is none of.
#[test]
fn a_nul_in_any_pattern_is_refused_before_its_field_is_looked_at() {
for field in 0..=10 {
for pattern in [&b"\0"[..], b"Alpha\0", b"\0Alpha", b"Al\0pha", b"a.c\0"] {
for case in [IdentifierCase::Sensitive, IdentifierCase::AsciiInsensitive] {
assert!(
matches!(Query::compile(field, pattern, case), Err(Refusal::Nul)),
"{field}, {pattern:?}, {case:?}"
);
}
}
}
}
}