1use winnow::combinator::{
11 alt, cut_err, delimited, eof, fail, opt, peek, preceded, repeat, separated, terminated,
12};
13use winnow::error::{ErrMode, ParserError};
14use winnow::prelude::*;
15use winnow::stream::{ContainsToken, Stream, TokenSlice};
16use winnow::token::{any, one_of};
17
18use crate::ast::{
19 Acceptability, AcceptabilitySet, AltIdentifier, Attribute, AttributeSet, AttributeValue,
20 Cardinality, Comparison, ConceptFilter, ConceptReference, ConceptSet, ConstraintOperator,
21 DefinitionStatus, DescriptionFilter, DialectAlias, DialectIdValue, Equality,
22 ExpressionConstraint, FieldValue, FilterConstraint, FocusConcept, HistorySupplement,
23 MemberFilter, MemberOf, NumericValue, Refinement, RefsetFields, Sctid, SubAttributeSet,
24 SubExpressionConstraint, SubRefinement, TimeValue, TypeToken, TypedSearchTerm,
25};
26use crate::lexer::{Kind, Token};
27
28pub type Tokens<'i> = TokenSlice<'i, Token<'i>>;
30type PResult<T> = ModalResult<T, Failure>;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
35pub struct Failure {
36 pub expected: Option<&'static str>,
38}
39
40impl<I: Stream> ParserError<I> for Failure {
41 type Inner = Self;
42
43 fn from_input(_: &I) -> Self {
44 Self::default()
45 }
46
47 fn into_inner(self) -> Result<Self::Inner, Self> {
48 Ok(self)
49 }
50}
51
52impl ContainsToken<&'_ Token<'_>> for Kind {
53 fn contains_token(&self, token: &Token<'_>) -> bool {
54 *self == token.kind
55 }
56}
57
58impl<const LEN: usize> ContainsToken<&'_ Token<'_>> for [Kind; LEN] {
59 fn contains_token(&self, token: &Token<'_>) -> bool {
60 self.contains(&token.kind)
61 }
62}
63
64trait Expecting<'i, O>: Parser<Tokens<'i>, O, ErrMode<Failure>> + Sized {
67 fn expecting(self, what: &'static str) -> impl Parser<Tokens<'i>, O, ErrMode<Failure>> {
68 let mut parser = self;
69 move |i: &mut Tokens<'i>| {
70 parser.parse_next(i).map_err(|error| match error {
71 ErrMode::Backtrack(_) => ErrMode::Backtrack(Failure {
72 expected: Some(what),
73 }),
74 other => other,
75 })
76 }
77 }
78}
79
80impl<'i, O, P: Parser<Tokens<'i>, O, ErrMode<Failure>>> Expecting<'i, O> for P {}
81
82fn backtrack<T>() -> PResult<T> {
83 Err(ErrMode::Backtrack(Failure::default()))
84}
85
86fn refuse<'i, T>(
89 i: &mut Tokens<'i>,
90 start: &<Tokens<'i> as Stream>::Checkpoint,
91 what: &'static str,
92) -> PResult<T> {
93 i.reset(start);
94 Err(ErrMode::Cut(Failure {
95 expected: Some(what),
96 }))
97}
98
99fn kind<'i>(kind: Kind) -> impl Parser<Tokens<'i>, &'i Token<'i>, ErrMode<Failure>> {
101 one_of(kind).expecting(kind.describe())
102}
103
104fn keyword<'i>(word: &'static str) -> impl Parser<Tokens<'i>, &'i Token<'i>, ErrMode<Failure>> {
107 any.verify(move |t: &Token<'i>| t.kind == Kind::Identifier && t.text.eq_ignore_ascii_case(word))
108 .expecting(word)
109}
110
111fn separated_by_space(previous: &Token<'_>, next: &Token<'_>) -> bool {
113 previous.span.end < next.span.start
114}
115
116fn adjacent(previous: &Token<'_>, next: &Token<'_>) -> bool {
118 previous.span.end == next.span.start
119}
120
121fn junction_word<'i>(word: &'static str) -> impl Parser<Tokens<'i>, (), ErrMode<Failure>> {
124 move |i: &mut Tokens<'i>| {
125 let word = keyword(word).parse_next(i)?;
126 let next = cut_err(peek(any).expecting("whitespace after the junction")).parse_next(i)?;
127 if !separated_by_space(word, next) {
128 return Err(ErrMode::Cut(Failure {
129 expected: Some("whitespace after the junction"),
130 }));
131 }
132 Ok(())
133 }
134}
135
136fn conjunction(i: &mut Tokens<'_>) -> PResult<()> {
138 alt((junction_word("AND"), kind(Kind::Comma).void())).parse_next(i)
139}
140
141fn disjunction(i: &mut Tokens<'_>) -> PResult<()> {
143 junction_word("OR").parse_next(i)
144}
145
146fn exclusion(i: &mut Tokens<'_>) -> PResult<()> {
148 junction_word("MINUS").parse_next(i)
149}
150
151fn inner<'i>(token: &Token<'i>, delimiter: char) -> PResult<&'i str> {
154 token
155 .text
156 .strip_prefix(delimiter)
157 .and_then(|t| t.strip_suffix(delimiter))
158 .map_or_else(backtrack, Ok)
159}
160
161fn spaced_set<'i, T>(
164 mut item: impl Parser<Tokens<'i>, T, ErrMode<Failure>>,
165 min: usize,
166 what: &'static str,
167) -> impl Parser<Tokens<'i>, Vec<T>, ErrMode<Failure>> {
168 move |i: &mut Tokens<'i>| {
169 kind(Kind::LeftParen).parse_next(i)?;
170 let mut items = vec![item.parse_next(i)?];
171 loop {
172 let start = i.checkpoint();
173 let Some(next) = i.peek_token() else { break };
174 let Some(previous) = i.previous_tokens().next() else {
175 break;
176 };
177 if !separated_by_space(previous, next) {
178 break;
179 }
180 match item.parse_next(i) {
181 Ok(value) => items.push(value),
182 Err(ErrMode::Backtrack(_)) => {
183 i.reset(&start);
184 break;
185 }
186 Err(e) => return Err(e),
187 }
188 }
189 if items.len() < min {
190 return Err(ErrMode::Backtrack(Failure {
191 expected: Some(what),
192 }));
193 }
194 kind(Kind::RightParen).parse_next(i)?;
195 Ok(items)
196 }
197}
198
199fn bare_or_set<'i, T>(
201 mut item: impl Parser<Tokens<'i>, T, ErrMode<Failure>>,
202 what: &'static str,
203) -> impl Parser<Tokens<'i>, Vec<T>, ErrMode<Failure>> {
204 move |i: &mut Tokens<'i>| {
205 if i.peek_token().is_some_and(|t| t.kind == Kind::LeftParen) {
206 return spaced_set(item.by_ref(), 1, what).parse_next(i);
207 }
208 item.parse_next(i).map(|one| vec![one])
209 }
210}
211
212fn sctid(i: &mut Tokens<'_>) -> PResult<Sctid> {
214 kind(Kind::Integer)
215 .verify_map(|t: &Token<'_>| {
216 let digits = t.text.len();
217 ((6..=18).contains(&digits) && !t.text.starts_with('0'))
218 .then(|| t.text.parse().ok().map(Sctid))
219 .flatten()
220 })
221 .expecting("a SNOMED CT identifier")
222 .parse_next(i)
223}
224
225fn term(i: &mut Tokens<'_>) -> PResult<String> {
228 let start = i.checkpoint();
229 let token = kind(Kind::Term).parse_next(i)?;
230 let text = inner(token, '|')?.trim();
231 if text.is_empty() || text.contains(['\t', '\r', '\n']) {
232 return refuse(i, &start, "a term on one line");
233 }
234 Ok(text.to_owned())
235}
236
237fn concept_reference(i: &mut Tokens<'_>) -> PResult<ConceptReference> {
239 let id = sctid.parse_next(i)?;
240 let term = opt(term).parse_next(i)?;
241 Ok(ConceptReference { id, term })
242}
243
244fn is_scheme_alias(text: &str) -> bool {
246 let mut chars = text.chars();
247 chars.next().is_some_and(|c| c.is_ascii_alphabetic())
248 && chars.all(|c| c.is_ascii_alphanumeric() || c == '-')
249}
250
251fn alt_identifier(i: &mut Tokens<'_>) -> PResult<AltIdentifier> {
253 let token = one_of([Kind::AltIdentifier, Kind::String]).parse_next(i)?;
254 let text = if token.kind == Kind::String {
255 inner(token, '"')?
256 } else {
257 token.text
258 };
259 let Some((scheme, code)) = text.split_once('#') else {
260 return backtrack();
261 };
262 if !is_scheme_alias(scheme) || code.is_empty() || code.contains('\\') {
263 return backtrack();
264 }
265 let term = opt(term).parse_next(i)?;
266 Ok(AltIdentifier {
267 scheme: scheme.to_owned(),
268 code: code.to_owned(),
269 term,
270 })
271}
272
273fn focus(i: &mut Tokens<'_>) -> PResult<FocusConcept> {
275 alt((
276 kind(Kind::Asterisk).value(FocusConcept::Wildcard),
277 concept_reference.map(FocusConcept::Reference),
278 alt_identifier.map(FocusConcept::AltIdentifier),
279 delimited(
280 kind(Kind::LeftParen),
281 expression_constraint,
282 kind(Kind::RightParen),
283 )
284 .map(|inner| FocusConcept::Nested(Box::new(inner))),
285 ))
286 .expecting("a concept reference, '*', an alternate identifier, or '('")
287 .parse_next(i)
288}
289
290fn constraint_operator(i: &mut Tokens<'_>) -> PResult<ConstraintOperator> {
292 any.verify_map(|t: &Token<'_>| match t.kind {
293 Kind::LessThan => Some(ConstraintOperator::DescendantOf),
294 Kind::DescendantOrSelfOf => Some(ConstraintOperator::DescendantOrSelfOf),
295 Kind::ChildOf => Some(ConstraintOperator::ChildOf),
296 Kind::ChildOrSelfOf => Some(ConstraintOperator::ChildOrSelfOf),
297 Kind::GreaterThan => Some(ConstraintOperator::AncestorOf),
298 Kind::AncestorOrSelfOf => Some(ConstraintOperator::AncestorOrSelfOf),
299 Kind::ParentOf => Some(ConstraintOperator::ParentOf),
300 Kind::ParentOrSelfOf => Some(ConstraintOperator::ParentOrSelfOf),
301 Kind::Top => Some(ConstraintOperator::Top),
302 Kind::Bottom => Some(ConstraintOperator::Bottom),
303 _ => None,
304 })
305 .expecting("a constraint operator")
306 .parse_next(i)
307}
308
309fn refset_field_name(i: &mut Tokens<'_>) -> PResult<String> {
311 kind(Kind::Identifier)
312 .verify(|t: &Token<'_>| t.text.bytes().all(|b| b.is_ascii_alphabetic()))
313 .map(|t| t.text.to_owned())
314 .expecting("a reference set field name")
315 .parse_next(i)
316}
317
318fn member_of(i: &mut Tokens<'_>) -> PResult<MemberOf> {
320 kind(Kind::Caret).parse_next(i)?;
321 let fields = opt(delimited(
322 kind(Kind::LeftBracket),
323 cut_err(alt((
324 kind(Kind::Asterisk).value(RefsetFields::Any),
325 separated(1.., refset_field_name, kind(Kind::Comma)).map(RefsetFields::Names),
326 ))),
327 cut_err(kind(Kind::RightBracket)),
328 ))
329 .parse_next(i)?;
330 Ok(MemberOf { fields })
331}
332
333fn sub_expression_constraint(i: &mut Tokens<'_>) -> PResult<SubExpressionConstraint> {
335 let operator = opt(constraint_operator).parse_next(i)?;
336 let member_of = opt(member_of).parse_next(i)?;
337 let focus = if operator.is_some() || member_of.is_some() {
338 cut_err(focus).parse_next(i)?
339 } else {
340 focus.parse_next(i)?
341 };
342 let member_filters = repeat(0.., member_filter_constraint).parse_next(i)?;
343 let filters = repeat(
344 0..,
345 alt((concept_filter_constraint, description_filter_constraint)),
346 )
347 .parse_next(i)?;
348 let history = opt(history_supplement).parse_next(i)?;
349 Ok(SubExpressionConstraint {
350 operator,
351 member_of,
352 focus,
353 member_filters,
354 filters,
355 history,
356 })
357}
358
359fn operands<'i>(
362 first: SubExpressionConstraint,
363 junction: impl Parser<Tokens<'i>, (), ErrMode<Failure>>,
364 i: &mut Tokens<'i>,
365) -> PResult<Option<Vec<SubExpressionConstraint>>> {
366 let rest: Option<Vec<SubExpressionConstraint>> = opt(repeat(
367 1..,
368 preceded(junction, cut_err(sub_expression_constraint)),
369 ))
370 .parse_next(i)?;
371 Ok(rest.map(|rest| {
372 let mut all = vec![first];
373 all.extend(rest);
374 all
375 }))
376}
377
378fn expression_constraint(i: &mut Tokens<'_>) -> PResult<ExpressionConstraint> {
381 let first = sub_expression_constraint.parse_next(i)?;
382 if opt(kind(Kind::Colon)).parse_next(i)?.is_some() {
383 let refinement = cut_err(refinement).parse_next(i)?;
384 return Ok(ExpressionConstraint::Refined {
385 focus: first,
386 refinement: Box::new(refinement),
387 });
388 }
389 if i.peek_token().is_some_and(|t| t.kind == Kind::Period) {
390 let attributes = repeat(
391 1..,
392 preceded(kind(Kind::Period), cut_err(sub_expression_constraint)),
393 )
394 .parse_next(i)?;
395 return Ok(ExpressionConstraint::Dotted {
396 focus: first,
397 attributes,
398 });
399 }
400 if let Some(all) = operands(first.clone(), conjunction, i)? {
401 return Ok(ExpressionConstraint::Conjunction(all));
402 }
403 if let Some(all) = operands(first.clone(), disjunction, i)? {
404 return Ok(ExpressionConstraint::Disjunction(all));
405 }
406 if let Some(right) =
407 opt(preceded(exclusion, cut_err(sub_expression_constraint))).parse_next(i)?
408 {
409 return Ok(ExpressionConstraint::Exclusion { left: first, right });
410 }
411 Ok(ExpressionConstraint::Sub(first))
412}
413
414fn non_negative_integer(token: &Token<'_>) -> Option<u32> {
416 (token.text == "0" || !token.text.starts_with('0'))
417 .then(|| token.text.parse().ok())
418 .flatten()
419}
420
421fn cardinality(i: &mut Tokens<'_>) -> PResult<Cardinality> {
423 let start = i.checkpoint();
424 let open = kind(Kind::LeftBracket).parse_next(i)?;
425 let (min, to, max, close) = cut_err(
426 (
427 kind(Kind::Integer),
428 kind(Kind::To),
429 one_of([Kind::Integer, Kind::Asterisk]),
430 kind(Kind::RightBracket),
431 )
432 .expecting("a cardinality such as [1..*]"),
433 )
434 .parse_next(i)?;
435 let tight =
436 adjacent(open, min) && adjacent(min, to) && adjacent(to, max) && adjacent(max, close);
437 let min_value = non_negative_integer(min);
438 let max_value = if max.kind == Kind::Asterisk {
439 Some(None)
440 } else {
441 non_negative_integer(max).map(Some)
442 };
443 match (tight, min_value, max_value) {
444 (true, Some(min), Some(max)) => Ok(Cardinality { min, max }),
445 _ => refuse(i, &start, "a cardinality such as [1..*]"),
446 }
447}
448
449fn equality(i: &mut Tokens<'_>) -> PResult<Equality> {
451 alt((
452 kind(Kind::Equal).value(Equality::Equal),
453 kind(Kind::NotEqual).value(Equality::NotEqual),
454 ))
455 .parse_next(i)
456}
457
458fn comparison(i: &mut Tokens<'_>) -> PResult<Comparison> {
460 alt((
461 kind(Kind::Equal).value(Comparison::Equal),
462 kind(Kind::NotEqual).value(Comparison::NotEqual),
463 kind(Kind::LessOrEqual).value(Comparison::LessOrEqual),
464 kind(Kind::LessThan).value(Comparison::Less),
465 kind(Kind::GreaterOrEqual).value(Comparison::GreaterOrEqual),
466 kind(Kind::GreaterThan).value(Comparison::Greater),
467 ))
468 .parse_next(i)
469}
470
471fn numeric_value(i: &mut Tokens<'_>) -> PResult<NumericValue> {
474 let start = i.checkpoint();
475 let hash = kind(Kind::Hash).parse_next(i)?;
476 let sign = opt(one_of([Kind::Dash, Kind::Plus])).parse_next(i)?;
477 let integer = cut_err(kind(Kind::Integer)).parse_next(i)?;
478 let fraction = opt((kind(Kind::Period), kind(Kind::Integer))).parse_next(i)?;
479 let mut previous = hash;
480 let mut text = String::new();
481 for token in sign
482 .into_iter()
483 .chain(std::iter::once(integer))
484 .chain(fraction.iter().flat_map(|(dot, digits)| [*dot, *digits]))
485 {
486 if !adjacent(previous, token) {
487 return refuse(i, &start, "a number written without spaces");
488 }
489 text.push_str(token.text);
490 previous = token;
491 }
492 if integer.text != "0" && integer.text.starts_with('0') {
493 return refuse(i, &start, "a number without a leading zero");
494 }
495 Ok(NumericValue(text))
496}
497
498fn boolean(i: &mut Tokens<'_>) -> PResult<bool> {
500 alt((keyword("true").value(true), keyword("false").value(false))).parse_next(i)
501}
502
503fn string_content<'i>(i: &mut Tokens<'i>) -> PResult<&'i str> {
505 let token = kind(Kind::String).parse_next(i)?;
506 inner(token, '"')
507}
508
509fn escapes_only(text: &str, allowed: &[char]) -> bool {
511 let mut chars = text.chars();
512 while let Some(c) = chars.next() {
513 if c == '\\' && !chars.next().is_some_and(|e| allowed.contains(&e)) {
514 return false;
515 }
516 }
517 true
518}
519
520fn match_words(text: &str) -> Option<Vec<String>> {
523 let mut words = Vec::new();
524 let mut word = String::new();
525 let mut rest = text;
526 while let Some(c) = rest.chars().next() {
527 if let Some(after) = rest.strip_prefix("/*") {
528 let end = after.find("*/")?;
529 rest = after.get(end.saturating_add(2)..)?;
530 if !word.is_empty() {
531 words.push(std::mem::take(&mut word));
532 }
533 continue;
534 }
535 rest = rest.get(c.len_utf8()..)?;
536 if matches!(c, ' ' | '\t' | '\r' | '\n') {
537 if !word.is_empty() {
538 words.push(std::mem::take(&mut word));
539 }
540 } else if c == '\\' {
541 let escaped = rest.chars().next().filter(|e| matches!(e, '"' | '\\'))?;
542 rest = rest.get(escaped.len_utf8()..)?;
543 word.push('\\');
544 word.push(escaped);
545 } else {
546 word.push(c);
547 }
548 }
549 if !word.is_empty() {
550 words.push(word);
551 }
552 (!words.is_empty()).then_some(words)
553}
554
555fn typed_search_term(i: &mut Tokens<'_>) -> PResult<TypedSearchTerm> {
557 if opt((keyword("wild"), kind(Kind::Colon)))
558 .parse_next(i)?
559 .is_some()
560 {
561 let start = i.checkpoint();
562 let pattern = cut_err(string_content).parse_next(i)?;
563 if pattern.is_empty() || !escapes_only(pattern, &['"', '\\', '*']) {
564 return refuse(i, &start, "a wildcard search term");
565 }
566 return Ok(TypedSearchTerm::Wild(pattern.to_owned()));
567 }
568 let explicit = opt((keyword("match"), kind(Kind::Colon)))
569 .parse_next(i)?
570 .is_some();
571 let start = i.checkpoint();
572 let content = if explicit {
573 cut_err(string_content).parse_next(i)?
574 } else {
575 string_content.parse_next(i)?
576 };
577 match match_words(content) {
578 Some(words) => Ok(TypedSearchTerm::Match(words)),
579 None => refuse(i, &start, "one or more search words"),
580 }
581}
582
583fn typed_search_terms(i: &mut Tokens<'_>) -> PResult<Vec<TypedSearchTerm>> {
585 bare_or_set(typed_search_term, "a search term").parse_next(i)
586}
587
588fn time_value(i: &mut Tokens<'_>) -> PResult<TimeValue> {
590 let start = i.checkpoint();
591 let text = string_content.parse_next(i)?;
592 let in_range = |range: std::ops::Range<usize>, max: u8| {
593 text.get(range)
594 .and_then(|part| part.parse::<u8>().ok())
595 .is_some_and(|value| (1..=max).contains(&value))
596 };
597 let valid = text.is_empty()
598 || (text.len() == 8
599 && text.bytes().all(|b| b.is_ascii_digit())
600 && !text.starts_with('0')
601 && in_range(4..6, 12)
602 && in_range(6..8, 31));
603 if !valid {
604 return refuse(i, &start, "a time as \"YYYYMMDD\" or \"\"");
605 }
606 Ok(TimeValue(text.to_owned()))
607}
608
609fn active_value(i: &mut Tokens<'_>) -> PResult<bool> {
611 alt((
612 kind(Kind::Integer).verify_map(|t: &Token<'_>| match t.text {
613 "1" => Some(true),
614 "0" => Some(false),
615 _ => None,
616 }),
617 boolean,
618 ))
619 .expecting("1, 0, true, or false")
620 .parse_next(i)
621}
622
623fn concept_set(i: &mut Tokens<'_>) -> PResult<ConceptSet> {
629 alt((
630 spaced_set(concept_reference, 2, "two or more concept references").map(ConceptSet::Set),
631 sub_expression_constraint.map(ConceptSet::Expression),
632 ))
633 .parse_next(i)
634}
635
636fn acceptability_set(i: &mut Tokens<'_>) -> PResult<AcceptabilitySet> {
638 alt((
639 spaced_set(concept_reference, 1, "acceptability concepts").map(AcceptabilitySet::Concepts),
640 spaced_set(
641 alt((
642 keyword("accept").value(Acceptability::Acceptable),
643 keyword("prefer").value(Acceptability::Preferred),
644 )),
645 1,
646 "accept or prefer",
647 )
648 .map(AcceptabilitySet::Tokens),
649 ))
650 .parse_next(i)
651}
652
653fn dialect_id_set(
656 i: &mut Tokens<'_>,
657) -> PResult<Vec<(ConceptReference, Option<AcceptabilitySet>)>> {
658 spaced_set(
659 (concept_reference, opt(acceptability_set)),
660 1,
661 "dialect reference sets",
662 )
663 .verify(
664 |items: &Vec<(ConceptReference, Option<AcceptabilitySet>)>| {
665 items.len() >= 2 || items.iter().any(|(_, a)| a.is_some())
666 },
667 )
668 .parse_next(i)
669}
670
671fn dialect_alias(i: &mut Tokens<'_>) -> PResult<String> {
673 kind(Kind::Identifier)
674 .verify(|t: &Token<'_>| is_scheme_alias(t.text))
675 .map(|t| t.text.to_owned())
676 .expecting("a dialect alias such as en-gb")
677 .parse_next(i)
678}
679
680fn language_code(i: &mut Tokens<'_>) -> PResult<String> {
682 kind(Kind::Identifier)
683 .verify(|t: &Token<'_>| {
684 t.text.len() == 2 && t.text.bytes().all(|b| b.is_ascii_alphabetic())
685 })
686 .map(|t| t.text.to_owned())
687 .expecting("a two-letter language code")
688 .parse_next(i)
689}
690
691fn type_token(i: &mut Tokens<'_>) -> PResult<TypeToken> {
693 alt((
694 keyword("syn").value(TypeToken::Synonym),
695 keyword("fsn").value(TypeToken::FullySpecifiedName),
696 keyword("def").value(TypeToken::Definition),
697 ))
698 .parse_next(i)
699}
700
701fn definition_status(i: &mut Tokens<'_>) -> PResult<DefinitionStatus> {
703 alt((
704 keyword("primitive").value(DefinitionStatus::Primitive),
705 keyword("defined").value(DefinitionStatus::Defined),
706 ))
707 .parse_next(i)
708}
709
710fn dialect_filter(i: &mut Tokens<'_>) -> PResult<DescriptionFilter> {
712 if opt(keyword("dialectId")).parse_next(i)?.is_some() {
713 let (operator, value, acceptability) = cut_err((
714 equality,
715 alt((
716 dialect_id_set.map(DialectIdValue::Set),
717 sub_expression_constraint.map(DialectIdValue::Expression),
718 )),
719 opt(acceptability_set),
720 ))
721 .parse_next(i)?;
722 return Ok(DescriptionFilter::DialectId {
723 operator,
724 value,
725 acceptability,
726 });
727 }
728 keyword("dialect").parse_next(i)?;
729 let (operator, aliases, acceptability) = cut_err((
730 equality,
731 alt((
732 spaced_set(
733 (dialect_alias, opt(acceptability_set)).map(|(alias, acceptability)| {
734 DialectAlias {
735 alias,
736 acceptability,
737 }
738 }),
739 1,
740 "dialect aliases",
741 ),
742 dialect_alias.map(|alias| {
743 vec![DialectAlias {
744 alias,
745 acceptability: None,
746 }]
747 }),
748 )),
749 opt(acceptability_set),
750 ))
751 .parse_next(i)?;
752 Ok(DescriptionFilter::Dialect {
753 operator,
754 aliases,
755 acceptability,
756 })
757}
758
759fn description_filter(i: &mut Tokens<'_>) -> PResult<DescriptionFilter> {
761 alt((
762 preceded(keyword("term"), cut_err((equality, typed_search_terms)))
763 .map(|(operator, terms)| DescriptionFilter::Term { operator, terms }),
764 preceded(
765 keyword("language"),
766 cut_err((equality, bare_or_set(language_code, "language codes"))),
767 )
768 .map(|(operator, codes)| DescriptionFilter::Language { operator, codes }),
769 preceded(keyword("typeId"), cut_err((equality, concept_set)))
770 .map(|(operator, value)| DescriptionFilter::TypeId { operator, value }),
771 preceded(
772 keyword("type"),
773 cut_err((equality, bare_or_set(type_token, "type tokens"))),
774 )
775 .map(|(operator, tokens)| DescriptionFilter::Type { operator, tokens }),
776 dialect_filter,
777 preceded(keyword("moduleId"), cut_err((equality, concept_set)))
778 .map(|(operator, value)| DescriptionFilter::Module { operator, value }),
779 preceded(
780 keyword("effectiveTime"),
781 cut_err((comparison, bare_or_set(time_value, "times"))),
782 )
783 .map(|(operator, values)| DescriptionFilter::EffectiveTime { operator, values }),
784 preceded(keyword("active"), cut_err((equality, active_value)))
785 .map(|(operator, value)| DescriptionFilter::Active { operator, value }),
786 preceded(
787 keyword("id"),
788 cut_err((equality, bare_or_set(sctid, "description identifiers"))),
789 )
790 .map(|(operator, ids)| DescriptionFilter::Id { operator, ids }),
791 ))
792 .expecting("a description filter")
793 .parse_next(i)
794}
795
796fn concept_filter(i: &mut Tokens<'_>) -> PResult<ConceptFilter> {
798 alt((
799 preceded(
800 keyword("definitionStatusId"),
801 cut_err((equality, concept_set)),
802 )
803 .map(|(operator, value)| ConceptFilter::DefinitionStatusId { operator, value }),
804 preceded(
805 keyword("definitionStatus"),
806 cut_err((
807 equality,
808 bare_or_set(definition_status, "definition status tokens"),
809 )),
810 )
811 .map(|(operator, tokens)| ConceptFilter::DefinitionStatus { operator, tokens }),
812 preceded(keyword("moduleId"), cut_err((equality, concept_set)))
813 .map(|(operator, value)| ConceptFilter::Module { operator, value }),
814 preceded(
815 keyword("effectiveTime"),
816 cut_err((comparison, bare_or_set(time_value, "times"))),
817 )
818 .map(|(operator, values)| ConceptFilter::EffectiveTime { operator, values }),
819 preceded(keyword("active"), cut_err((equality, active_value)))
820 .map(|(operator, value)| ConceptFilter::Active { operator, value }),
821 ))
822 .expecting("a concept filter")
823 .parse_next(i)
824}
825
826fn field_value(i: &mut Tokens<'_>) -> PResult<FieldValue> {
828 alt((
829 (equality, sub_expression_constraint)
830 .map(|(operator, value)| FieldValue::Expression { operator, value }),
831 (comparison, numeric_value)
832 .map(|(operator, value)| FieldValue::Numeric { operator, value }),
833 (equality, typed_search_terms)
834 .map(|(operator, terms)| FieldValue::String { operator, terms }),
835 (equality, boolean).map(|(operator, value)| FieldValue::Boolean { operator, value }),
836 (comparison, bare_or_set(time_value, "times"))
837 .map(|(operator, values)| FieldValue::Time { operator, values }),
838 preceded(
839 alt((equality.void(), comparison.void())),
840 cut_err(fail.expecting("a member field value")),
841 ),
842 ))
843 .expecting("a comparison operator")
844 .parse_next(i)
845}
846
847fn member_filter(i: &mut Tokens<'_>) -> PResult<MemberFilter> {
849 alt((
850 preceded(keyword("moduleId"), cut_err((equality, concept_set)))
851 .map(|(operator, value)| MemberFilter::Module { operator, value }),
852 preceded(
853 keyword("effectiveTime"),
854 cut_err((comparison, bare_or_set(time_value, "times"))),
855 )
856 .map(|(operator, values)| MemberFilter::EffectiveTime { operator, values }),
857 preceded(keyword("active"), cut_err((equality, active_value)))
858 .map(|(operator, value)| MemberFilter::Active { operator, value }),
859 (refset_field_name, cut_err(field_value))
860 .map(|(name, value)| MemberFilter::Field { name, value }),
861 ))
862 .expecting("a member filter")
863 .parse_next(i)
864}
865
866fn member_filter_constraint(i: &mut Tokens<'_>) -> PResult<Vec<MemberFilter>> {
868 preceded(
869 (kind(Kind::DoubleLeftBrace), keyword("M")),
870 cut_err(terminated(
871 separated(1.., member_filter, kind(Kind::Comma)),
872 kind(Kind::DoubleRightBrace),
873 )),
874 )
875 .parse_next(i)
876}
877
878fn concept_filter_constraint(i: &mut Tokens<'_>) -> PResult<FilterConstraint> {
880 preceded(
881 (kind(Kind::DoubleLeftBrace), keyword("C")),
882 cut_err(terminated(
883 separated(1.., concept_filter, kind(Kind::Comma)),
884 kind(Kind::DoubleRightBrace),
885 )),
886 )
887 .map(FilterConstraint::Concept)
888 .parse_next(i)
889}
890
891fn description_filter_constraint(i: &mut Tokens<'_>) -> PResult<FilterConstraint> {
894 kind(Kind::DoubleLeftBrace).parse_next(i)?;
895 let starts_other = i.peek_token().is_some_and(|t| {
896 t.kind == Kind::Plus
897 || (t.kind == Kind::Identifier
898 && (t.text.eq_ignore_ascii_case("M") || t.text.eq_ignore_ascii_case("C")))
899 });
900 if starts_other {
901 return backtrack();
902 }
903 opt(keyword("D")).parse_next(i)?;
904 cut_err(terminated(
905 separated(1.., description_filter, kind(Kind::Comma)),
906 kind(Kind::DoubleRightBrace),
907 ))
908 .map(FilterConstraint::Description)
909 .parse_next(i)
910}
911
912fn history_supplement(i: &mut Tokens<'_>) -> PResult<HistorySupplement> {
914 (kind(Kind::DoubleLeftBrace), kind(Kind::Plus)).parse_next(i)?;
915 let start = i.checkpoint();
916 let word = cut_err(kind(Kind::Identifier).expecting("HISTORY")).parse_next(i)?;
917 let profile = match word.text.to_ascii_uppercase().as_str() {
918 "HISTORY" => None,
919 "HISTORY-MIN" | "HISTORY_MIN" => Some(HistorySupplement::Minimum),
920 "HISTORY-MOD" | "HISTORY_MOD" => Some(HistorySupplement::Moderate),
921 "HISTORY-MAX" | "HISTORY_MAX" => Some(HistorySupplement::Maximum),
922 _ => {
923 return refuse(
924 i,
925 &start,
926 "HISTORY, HISTORY-MIN, HISTORY-MOD, or HISTORY-MAX",
927 );
928 }
929 };
930 let supplement = match profile {
931 Some(profile) => profile,
932 None => match opt(delimited(
933 kind(Kind::LeftParen),
934 cut_err(expression_constraint),
935 cut_err(kind(Kind::RightParen)),
936 ))
937 .parse_next(i)?
938 {
939 Some(subset) => HistorySupplement::Subset(Box::new(subset)),
940 None => HistorySupplement::Default,
941 },
942 };
943 cut_err(kind(Kind::DoubleRightBrace)).parse_next(i)?;
944 Ok(supplement)
945}
946
947fn attribute(i: &mut Tokens<'_>) -> PResult<Attribute> {
950 let cardinality = opt(cardinality).parse_next(i)?;
951 let reverse = opt(keyword("R")).parse_next(i)?.is_some();
952 let name = if reverse {
953 cut_err(sub_expression_constraint).parse_next(i)?
954 } else {
955 sub_expression_constraint.parse_next(i)?
956 };
957 let value = cut_err(attribute_value).parse_next(i)?;
958 Ok(Attribute {
959 cardinality,
960 reverse,
961 name,
962 value,
963 })
964}
965
966fn attribute_value(i: &mut Tokens<'_>) -> PResult<AttributeValue> {
968 alt((
969 (equality, sub_expression_constraint)
970 .map(|(operator, value)| AttributeValue::Expression { operator, value }),
971 (comparison, numeric_value)
972 .map(|(operator, value)| AttributeValue::Numeric { operator, value }),
973 (equality, typed_search_terms)
974 .map(|(operator, terms)| AttributeValue::String { operator, terms }),
975 (equality, boolean).map(|(operator, value)| AttributeValue::Boolean { operator, value }),
976 preceded(
977 alt((equality.void(), comparison.void())),
978 cut_err(fail.expecting("an attribute value")),
979 ),
980 ))
981 .expecting("a comparison operator")
982 .parse_next(i)
983}
984
985fn sub_attribute_set(i: &mut Tokens<'_>) -> PResult<SubAttributeSet> {
987 alt((
988 attribute.map(|attribute| SubAttributeSet::Attribute(Box::new(attribute))),
989 delimited(kind(Kind::LeftParen), attribute_set, kind(Kind::RightParen))
990 .map(|set| SubAttributeSet::Nested(Box::new(set))),
991 ))
992 .parse_next(i)
993}
994
995fn joined<'i, T: Clone>(
997 first: &T,
998 junction: impl Parser<Tokens<'i>, (), ErrMode<Failure>>,
999 item: impl Parser<Tokens<'i>, T, ErrMode<Failure>>,
1000 i: &mut Tokens<'i>,
1001) -> PResult<Option<Vec<T>>> {
1002 let rest: Option<Vec<T>> = opt(repeat(1.., preceded(junction, item))).parse_next(i)?;
1003 Ok(rest.map(|rest| {
1004 let mut all = vec![first.clone()];
1005 all.extend(rest);
1006 all
1007 }))
1008}
1009
1010fn attribute_set(i: &mut Tokens<'_>) -> PResult<AttributeSet> {
1012 let first = sub_attribute_set.parse_next(i)?;
1013 if let Some(all) = joined(&first, conjunction, sub_attribute_set, i)? {
1014 return Ok(AttributeSet::Conjunction(all));
1015 }
1016 if let Some(all) = joined(&first, disjunction, sub_attribute_set, i)? {
1017 return Ok(AttributeSet::Disjunction(all));
1018 }
1019 Ok(AttributeSet::Single(Box::new(first)))
1020}
1021
1022fn attribute_group(i: &mut Tokens<'_>) -> PResult<SubRefinement> {
1024 let cardinality = opt(cardinality).parse_next(i)?;
1025 kind(Kind::LeftBrace).parse_next(i)?;
1026 let attributes = cut_err(attribute_set).parse_next(i)?;
1027 cut_err(kind(Kind::RightBrace)).parse_next(i)?;
1028 Ok(SubRefinement::Group {
1029 cardinality,
1030 attributes,
1031 })
1032}
1033
1034fn sub_refinement(i: &mut Tokens<'_>) -> PResult<SubRefinement> {
1037 alt((
1038 attribute_set.map(SubRefinement::AttributeSet),
1039 attribute_group,
1040 delimited(
1041 kind(Kind::LeftParen),
1042 cut_err(refinement),
1043 cut_err(kind(Kind::RightParen)),
1044 )
1045 .map(|inner| SubRefinement::Nested(Box::new(inner))),
1046 ))
1047 .expecting("an attribute, an attribute group, or '('")
1048 .parse_next(i)
1049}
1050
1051fn refinement(i: &mut Tokens<'_>) -> PResult<Refinement> {
1053 let first = sub_refinement.parse_next(i)?;
1054 if let Some(all) = joined(&first, conjunction, cut_err(sub_refinement), i)? {
1055 return Ok(Refinement::Conjunction(all));
1056 }
1057 if let Some(all) = joined(&first, disjunction, cut_err(sub_refinement), i)? {
1058 return Ok(Refinement::Disjunction(all));
1059 }
1060 Ok(Refinement::Single(Box::new(first)))
1061}
1062
1063pub fn whole(i: &mut Tokens<'_>) -> PResult<ExpressionConstraint> {
1070 let constraint = expression_constraint
1071 .expecting("an expression constraint")
1072 .parse_next(i)?;
1073 eof.expecting("the end of the expression").parse_next(i)?;
1074 Ok(constraint)
1075}