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