1use std::fmt;
3mod descriptions;
4mod filters;
5pub use descriptions::{DescriptionFilter, Dialect};
6mod history;
7mod identifiers;
8mod members;
9mod refinement;
10mod search;
11pub use filters::ConceptFilter;
12pub use history::History;
13pub use members::{MemberFilter, MemberPredicate, MemberQuery};
14pub use refinement::{AttributeConstraint, AttributeValue, Cardinality, Comparison, Refinement};
15pub use search::SearchTerm;
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum Hierarchy {
19 Descendant,
20 DescendantOrSelf,
21 Child,
22 ChildOrSelf,
23 Ancestor,
24 AncestorOrSelf,
25 Parent,
26 ParentOrSelf,
27}
28
29impl Hierarchy {
30 pub fn ancestors(self) -> bool {
31 matches!(
32 self,
33 Self::Ancestor | Self::AncestorOrSelf | Self::Parent | Self::ParentOrSelf
34 )
35 }
36 pub fn direct(self) -> bool {
37 matches!(
38 self,
39 Self::Child | Self::ChildOrSelf | Self::Parent | Self::ParentOrSelf
40 )
41 }
42 pub fn include_self(self) -> bool {
43 matches!(
44 self,
45 Self::DescendantOrSelf | Self::ChildOrSelf | Self::AncestorOrSelf | Self::ParentOrSelf
46 )
47 }
48}
49
50#[derive(Clone, Debug, PartialEq, Eq)]
51pub enum Expr {
52 Concept(u64),
53 AlternateIdentifier { scheme: String, code: String },
54 DialectAlias(String),
55 All,
56 Hierarchy(Hierarchy, Box<Expr>),
57 And(Vec<Expr>),
58 Or(Vec<Expr>),
59 Minus(Box<Expr>, Box<Expr>),
60 Refined(Box<Expr>, Box<Refinement>),
61 Dotted(Box<Expr>, Vec<Expr>),
62 Extremum { top: bool, inner: Box<Expr> },
63 MemberOf(Box<Expr>),
64 Members(MemberQuery),
65 History(Box<Expr>, History),
66 RefsetContainingAny(Box<Expr>),
67 ConceptFiltered(Box<Expr>, Vec<ConceptFilter>),
68 DescriptionFiltered(Box<Expr>, Vec<DescriptionFilter>),
69}
70
71#[derive(Clone, Copy, Debug, PartialEq, Eq)]
72pub enum ParseErrorKind {
73 Syntax,
74 Unsupported,
76 Semantic,
78 Limit,
79}
80
81#[derive(Clone, Debug, PartialEq, Eq)]
82pub struct ParseError {
83 pub kind: ParseErrorKind,
84 pub offset: usize,
85 pub message: &'static str,
86}
87impl fmt::Display for ParseError {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 write!(
90 f,
91 "{:?} at byte {}: {}",
92 self.kind, self.offset, self.message
93 )
94 }
95}
96impl std::error::Error for ParseError {}
97type Result<T> = std::result::Result<T, ParseError>;
98
99pub const MAX_QUERY_BYTES: usize = 65536;
100pub const MAX_DEPTH: usize = 64;
101pub const MAX_NODES: usize = 4096;
102
103pub fn parse(text: &str) -> Result<Expr> {
104 let mut parser = Parser {
105 text,
106 pos: 0,
107 nodes: 0,
108 refused: None,
109 };
110 if text.len() > MAX_QUERY_BYTES {
111 return Err(parser.error(ParseErrorKind::Limit, "Query exceeds 65536 bytes"));
112 }
113 let expression = parser.expression(0)?;
114 parser.ws()?;
115 if parser.pos != text.len() {
116 return Err(parser.unexpected());
117 }
118 if let Some(refusal) = parser.refused {
121 return Err(refusal);
122 }
123 Ok(expression)
124}
125
126#[derive(Clone, Copy, PartialEq, Eq)]
127enum Boolean {
128 And,
129 Or,
130 Minus,
131}
132
133struct Parser<'a> {
134 text: &'a str,
135 pos: usize,
136 nodes: usize,
137 refused: Option<ParseError>,
139}
140
141#[derive(Clone)]
143struct Mark {
144 pos: usize,
145 nodes: usize,
146 refused: Option<ParseError>,
147}
148
149impl Parser<'_> {
150 fn mark(&self) -> Mark {
151 Mark {
152 pos: self.pos,
153 nodes: self.nodes,
154 refused: self.refused.clone(),
155 }
156 }
157 fn reset(&mut self, mark: Mark) {
158 self.pos = mark.pos;
159 self.nodes = mark.nodes;
160 self.refused = mark.refused;
161 }
162 fn refuse(&mut self, at: usize, message: &'static str) {
164 self.refused.get_or_insert(ParseError {
165 kind: ParseErrorKind::Semantic,
166 offset: at,
167 message,
168 });
169 }
170 fn error(&self, kind: ParseErrorKind, message: &'static str) -> ParseError {
171 ParseError {
172 kind,
173 offset: self.pos,
174 message,
175 }
176 }
177 fn rest(&self) -> &str {
178 &self.text[self.pos..]
179 }
180 fn take(&mut self, value: &str) -> bool {
181 if self.rest().starts_with(value) {
182 self.pos += value.len();
183 true
184 } else {
185 false
186 }
187 }
188 fn ws(&mut self) -> Result<bool> {
189 let start = self.pos;
190 loop {
191 while self.rest().starts_with([' ', '\t', '\r', '\n']) {
192 self.pos += 1;
193 }
194 if !self.take("/*") {
195 break;
196 }
197 let end = self
198 .rest()
199 .find("*/")
200 .ok_or_else(|| self.error(ParseErrorKind::Syntax, "Unclosed comment"))?;
201 if self.rest()[..end]
202 .chars()
203 .any(|c| c.is_ascii_control() && !matches!(c, '\t' | '\r' | '\n'))
204 {
205 return Err(self.error(ParseErrorKind::Syntax, "Invalid comment character"));
206 }
207 self.pos += end + 2;
208 }
209 Ok(self.pos != start)
210 }
211 fn word(&self) -> &str {
212 let end = self
213 .rest()
214 .bytes()
215 .take_while(u8::is_ascii_alphabetic)
216 .count();
217 &self.rest()[..end]
218 }
219 fn required_ws(&mut self) -> Result<()> {
220 if self.ws()? {
221 Ok(())
222 } else {
223 Err(self.error(
224 ParseErrorKind::Syntax,
225 "Keyword requires following whitespace or comment",
226 ))
227 }
228 }
229 fn node(&mut self, expression: Expr) -> Result<Expr> {
230 self.nodes += 1;
231 if self.nodes > MAX_NODES {
232 return Err(self.error(ParseErrorKind::Limit, "Too many expression nodes"));
233 }
234 Ok(expression)
235 }
236 fn boolean(&mut self) -> Result<Option<Boolean>> {
237 self.ws()?;
238 if self.take(",") {
239 return Ok(Some(Boolean::And));
240 }
241 let word = self.word();
242 let op = if word.eq_ignore_ascii_case("and") {
243 Boolean::And
244 } else if word.eq_ignore_ascii_case("or") {
245 Boolean::Or
246 } else if word.eq_ignore_ascii_case("minus") {
247 Boolean::Minus
248 } else {
249 return Ok(None);
250 };
251 self.pos += word.len();
252 self.required_ws()?;
253 Ok(Some(op))
254 }
255 fn term(&mut self) -> Result<()> {
256 let original = self.pos;
257 let after_ws = match self.ws() {
258 Ok(_) => self.pos,
259 Err(_) => original,
260 };
261 for start in [after_ws, original] {
263 self.pos = start;
264 let mut last_non_space = false;
265 while let Some(character) = self.rest().chars().next() {
266 if last_non_space {
267 let saved = self.pos;
268 if self.ws().is_ok() && self.take("|") {
269 return Ok(());
270 }
271 self.pos = saved;
272 }
273 if character == '|' || character.is_ascii_control() {
274 break;
275 }
276 last_non_space = character != ' ';
277 self.pos += character.len_utf8();
278 }
279 }
280 self.pos = original;
281 Err(self.error(ParseErrorKind::Syntax, "Invalid or unclosed concept term"))
282 }
283 fn expression(&mut self, depth: usize) -> Result<Expr> {
284 if depth > MAX_DEPTH {
285 return Err(self.error(ParseErrorKind::Limit, "Expression nesting exceeds 64"));
286 }
287 let left = self.subexpression(depth)?;
288 if self.take(":") {
289 let refinement = self.refinement(depth + 1)?;
290 return self.node(Expr::Refined(Box::new(left), Box::new(refinement)));
291 }
292 if self.take(".") {
293 let mut attributes = vec![self.subexpression(depth + 1)?];
294 while self.take(".") {
295 attributes.push(self.subexpression(depth + 1)?);
296 }
297 return self.node(Expr::Dotted(Box::new(left), attributes));
298 }
299 let Some(op) = self.boolean()? else {
300 return Ok(left);
301 };
302 let right = self.subexpression(depth)?;
303 if op == Boolean::Minus {
304 if self.boolean()?.is_some() {
305 return Err(self.error(
306 ParseErrorKind::Syntax,
307 "Parentheses required around mixed or repeated exclusion",
308 ));
309 }
310 return self.node(Expr::Minus(Box::new(left), Box::new(right)));
311 }
312 let mut operands = vec![left, right];
313 while let Some(next) = self.boolean()? {
314 if next != op {
315 return Err(self.error(
316 ParseErrorKind::Syntax,
317 "Mixed Boolean operators require parentheses",
318 ));
319 }
320 operands.push(self.subexpression(depth)?);
321 }
322 self.node(if op == Boolean::And {
323 Expr::And(operands)
324 } else {
325 Expr::Or(operands)
326 })
327 }
328 fn subexpression(&mut self, depth: usize) -> Result<Expr> {
329 self.ws()?;
330 if depth > MAX_DEPTH {
331 return Err(self.error(ParseErrorKind::Limit, "Expression nesting exceeds 64"));
332 }
333 let extremum = if self.take("!!>") {
334 Some(true)
335 } else if self.take("!!<") {
336 Some(false)
337 } else if !self.starts_alternate() && self.keyword("top") {
338 self.required_ws()?;
339 Some(true)
340 } else if !self.starts_alternate() && self.keyword("bottom") {
341 self.required_ws()?;
342 Some(false)
343 } else {
344 None
345 };
346 self.ws()?;
347 let mut hierarchy = None;
348 for (symbol, op) in [
349 ("<<!", Hierarchy::ChildOrSelf),
350 (">>!", Hierarchy::ParentOrSelf),
351 ("<<", Hierarchy::DescendantOrSelf),
352 (">>", Hierarchy::AncestorOrSelf),
353 ("<!", Hierarchy::Child),
354 (">!", Hierarchy::Parent),
355 ("<", Hierarchy::Descendant),
356 (">", Hierarchy::Ancestor),
357 ] {
358 if self.take(symbol) {
359 hierarchy = Some(op);
360 break;
361 }
362 }
363 if hierarchy.is_none() && !self.starts_alternate() {
364 let word = self.word();
365 for (name, op) in [
366 ("descendantof", Hierarchy::Descendant),
367 ("descendantorselfof", Hierarchy::DescendantOrSelf),
368 ("childof", Hierarchy::Child),
369 ("childorselfof", Hierarchy::ChildOrSelf),
370 ("ancestorof", Hierarchy::Ancestor),
371 ("ancestororselfof", Hierarchy::AncestorOrSelf),
372 ("parentof", Hierarchy::Parent),
373 ("parentorselfof", Hierarchy::ParentOrSelf),
374 ] {
375 if word.eq_ignore_ascii_case(name) {
376 hierarchy = Some(op);
377 self.pos += word.len();
378 self.required_ws()?;
379 break;
380 }
381 }
382 }
383 self.ws()?;
384 if extremum.is_some() && hierarchy.is_some() {
385 return Err(self.error(
386 ParseErrorKind::Syntax,
387 "Unary operators require a parenthesised operand",
388 ));
389 }
390 let alternate_scheme_r = {
396 let bytes = self.rest().as_bytes();
397 bytes.len() > 2
398 && bytes[..2].eq_ignore_ascii_case(b"^r")
399 && !bytes[2].is_ascii_alphabetic()
400 && bytes[2..]
401 .iter()
402 .find(|b| !(b.is_ascii_alphanumeric() || **b == b'-'))
403 == Some(&b'#')
404 };
405 let refset_operator = if !alternate_scheme_r && (self.take("^R") || self.take("^r")) {
406 Some(true)
407 } else if self.take("^") {
408 Some(false)
409 } else if !self.starts_alternate() && self.operator_keyword("memberOf") {
410 self.ws()?;
411 Some(false)
412 } else if !self.starts_alternate() && self.operator_keyword("refsetContainingAny") {
413 self.ws()?;
414 Some(true)
415 } else {
416 None
417 };
418 self.ws()?;
419 let fields = if refset_operator == Some(false) && self.rest().starts_with('[') {
420 Some(self.member_fields()?)
421 } else {
422 None
423 };
424 let mut expression = if self.take("(") {
425 let inner = self.expression(depth + 1)?;
426 self.ws()?;
427 if !self.take(")") {
428 return Err(self.unexpected());
429 }
430 inner
431 } else if self.starts_alternate() {
432 self.alternate_identifier()?
433 } else if self.take("*") || self.value_keyword("any") {
434 self.node(Expr::All)?
435 } else if self.rest().starts_with(|c: char| c.is_ascii_digit()) {
436 let start = self.pos;
437 while self.rest().starts_with(|c: char| c.is_ascii_digit()) {
438 self.pos += 1;
439 }
440 let code = &self.text[start..self.pos];
441 if !(6..=18).contains(&code.len()) || code.starts_with('0') {
442 return Err(self.error(
443 ParseErrorKind::Syntax,
444 "SCTID must contain 6 to 18 digits without a leading zero",
445 ));
446 }
447 let code = code
448 .parse()
449 .map_err(|_| self.error(ParseErrorKind::Syntax, "Invalid SCTID"))?;
450 self.ws()?;
451 if self.take("|") {
452 self.term()?;
453 }
454 self.node(Expr::Concept(code))?
455 } else {
456 return Err(self.unexpected());
457 };
458 self.ws()?;
459 let mut member_filters = Vec::new();
460 while self.starts_member_filter()? {
461 if refset_operator.is_none() {
462 self.refuse(
464 self.pos,
465 "Member filters require a refset operator (^ or ^R); ECL defines them only over memberOf rows",
466 );
467 }
468 member_filters.extend(self.member_filters(depth + 1)?);
469 self.ws()?;
470 }
471 if let Some(reverse) = refset_operator {
472 expression = self.node(if fields.is_some() || !member_filters.is_empty() {
473 Expr::Members(MemberQuery {
474 source: Box::new(expression),
475 reverse,
476 fields,
477 filters: member_filters,
478 })
479 } else if reverse {
480 Expr::RefsetContainingAny(Box::new(expression))
481 } else {
482 Expr::MemberOf(Box::new(expression))
483 })?;
484 }
485 if let Some(op) = hierarchy {
486 expression = self.node(Expr::Hierarchy(op, Box::new(expression)))?;
487 }
488 if let Some(top) = extremum {
489 expression = self.node(Expr::Extremum {
490 top,
491 inner: Box::new(expression),
492 })?;
493 }
494 while self.rest().starts_with("{{") {
495 let saved = self.pos;
496 self.take("{{");
497 self.ws()?;
498 let concept = self.rest().starts_with(['C', 'c']);
499 let history = self.rest().starts_with('+');
500 self.pos = saved;
501 if history {
502 let supplement = self.history(depth + 1)?;
503 expression = self.node(Expr::History(Box::new(expression), supplement))?;
504 self.ws()?;
505 break;
506 } else if concept {
507 let filters = self.concept_filters(depth + 1)?;
508 expression = self.node(Expr::ConceptFiltered(Box::new(expression), filters))?;
509 } else {
510 let filters = self.description_filters(depth + 1)?;
513 expression = self.node(Expr::DescriptionFiltered(Box::new(expression), filters))?;
514 }
515 self.ws()?;
516 }
517 Ok(expression)
518 }
519 fn operator_keyword(&mut self, word: &str) -> bool {
522 let found = self.word();
523 let joined = found.len() == word.len() + 3
524 && found[..word.len()].eq_ignore_ascii_case(word)
525 && found[word.len()..].eq_ignore_ascii_case("any");
526 if found.eq_ignore_ascii_case(word) || joined {
527 self.pos += word.len();
528 true
529 } else {
530 false
531 }
532 }
533 fn unexpected(&self) -> ParseError {
534 self.error(
535 ParseErrorKind::Syntax,
536 "Unexpected token or missing operand",
537 )
538 }
539}