1use winnow::combinator::{
14 alt, cut_err, delimited, eof, fail, opt, peek, preceded, repeat, separated,
15};
16use winnow::error::ErrMode;
17use winnow::prelude::*;
18use winnow::stream::Stream;
19use winnow::token::any;
20
21use crate::ast::{
22 AltIdentifier, Attribute, AttributeSet, AttributeValue, Cardinality, Comparison,
23 ConceptReference, ConcreteString, ConstraintOperator, Equality, ExpressionConstraint,
24 FocusConcept, HistorySupplement, MemberOf, NumericValue, Refinement, RefsetFields,
25 RefsetOperator, Sctid, SubAttributeSet, SubExpressionConstraint, SubRefinement,
26 TypedSearchTerm,
27};
28use crate::lexer::Kind;
29use sct_scg::front::{
30 Expecting, Failure, PResult, Token, Tokens, backtrack, kind, one_of_kinds, refuse,
31};
32
33mod filter;
34
35use self::filter::{
36 concept_filter_constraint, description_filter_constraint, member_filter_constraint,
37};
38
39fn keyword<'i>(
42 word: &'static str,
43) -> impl Parser<Tokens<'i, Kind>, &'i Token<'i, Kind>, ErrMode<Failure>> {
44 any.verify(move |t: &Token<'i, Kind>| {
45 t.kind == Kind::Identifier && t.text.eq_ignore_ascii_case(word)
46 })
47 .expecting(word)
48}
49
50fn separated_by_space(previous: &Token<'_, Kind>, next: &Token<'_, Kind>) -> bool {
52 previous.span.end < next.span.start
53}
54
55fn adjacent(previous: &Token<'_, Kind>, next: &Token<'_, Kind>) -> bool {
57 previous.span.end == next.span.start
58}
59
60fn junction_word<'i>(word: &'static str) -> impl Parser<Tokens<'i, Kind>, (), ErrMode<Failure>> {
63 move |i: &mut Tokens<'i, Kind>| {
64 let word = keyword(word).parse_next(i)?;
65 let next = cut_err(peek(any).expecting("whitespace after the junction")).parse_next(i)?;
66 if !separated_by_space(word, next) {
67 return Err(ErrMode::Cut(Failure {
68 expected: Some("whitespace after the junction"),
69 }));
70 }
71 Ok(())
72 }
73}
74
75fn conjunction(i: &mut Tokens<'_, Kind>) -> PResult<()> {
77 alt((junction_word("AND"), kind(Kind::Comma).void())).parse_next(i)
78}
79
80fn disjunction(i: &mut Tokens<'_, Kind>) -> PResult<()> {
82 junction_word("OR").parse_next(i)
83}
84
85fn exclusion(i: &mut Tokens<'_, Kind>) -> PResult<()> {
87 junction_word("MINUS").parse_next(i)
88}
89
90fn inner<'i>(token: &Token<'i, Kind>, delimiter: char) -> PResult<&'i str> {
93 token
94 .text
95 .strip_prefix(delimiter)
96 .and_then(|t| t.strip_suffix(delimiter))
97 .map_or_else(backtrack, Ok)
98}
99
100fn spaced_set<'i, T>(
103 mut item: impl Parser<Tokens<'i, Kind>, T, ErrMode<Failure>>,
104 min: usize,
105 what: &'static str,
106) -> impl Parser<Tokens<'i, Kind>, Vec<T>, ErrMode<Failure>> {
107 move |i: &mut Tokens<'i, Kind>| {
108 kind(Kind::LeftParen).parse_next(i)?;
109 let mut items = vec![item.parse_next(i)?];
110 loop {
111 let start = i.checkpoint();
112 let Some(next) = i.peek_token() else { break };
113 let Some(previous) = i.previous_tokens().next() else {
114 break;
115 };
116 if !separated_by_space(previous, next) {
117 break;
118 }
119 match item.parse_next(i) {
120 Ok(value) => items.push(value),
121 Err(ErrMode::Backtrack(_)) => {
122 i.reset(&start);
123 break;
124 }
125 Err(e) => return Err(e),
126 }
127 }
128 if items.len() < min {
129 return Err(ErrMode::Backtrack(Failure {
130 expected: Some(what),
131 }));
132 }
133 kind(Kind::RightParen).parse_next(i)?;
134 Ok(items)
135 }
136}
137
138fn bare_or_set<'i, T>(
140 mut item: impl Parser<Tokens<'i, Kind>, T, ErrMode<Failure>>,
141 what: &'static str,
142) -> impl Parser<Tokens<'i, Kind>, Vec<T>, ErrMode<Failure>> {
143 move |i: &mut Tokens<'i, Kind>| {
144 if i.peek_token().is_some_and(|t| t.kind == Kind::LeftParen) {
145 return spaced_set(item.by_ref(), 1, what).parse_next(i);
146 }
147 item.parse_next(i).map(|one| vec![one])
148 }
149}
150
151fn sctid(i: &mut Tokens<'_, Kind>) -> PResult<Sctid> {
153 kind(Kind::Integer)
154 .verify_map(|t: &Token<'_, Kind>| sctid_text(t.text))
155 .expecting("a SNOMED CT identifier")
156 .parse_next(i)
157}
158
159fn sctid_text(digits: &str) -> Option<Sctid> {
161 ((6..=18).contains(&digits.len()) && !digits.starts_with('0'))
162 .then(|| digits.parse().ok().map(Sctid))
163 .flatten()
164}
165
166fn term(i: &mut Tokens<'_, Kind>) -> PResult<String> {
169 let start = i.checkpoint();
170 let token = kind(Kind::Term).parse_next(i)?;
171 let text = inner(token, '|')?.trim();
172 if text.is_empty() || text.contains(['\t', '\r', '\n']) {
173 return refuse(i, &start, "a term on one line");
174 }
175 Ok(text.to_owned())
176}
177
178fn concept_reference(i: &mut Tokens<'_, Kind>) -> PResult<ConceptReference> {
180 let id = sctid.parse_next(i)?;
181 let term = opt(term).parse_next(i)?;
182 Ok(ConceptReference { id, term })
183}
184
185fn is_scheme_alias(text: &str) -> bool {
187 let mut chars = text.chars();
188 chars.next().is_some_and(|c| c.is_ascii_alphabetic())
189 && chars.all(|c| c.is_ascii_alphanumeric() || c == '-')
190}
191
192fn alt_identifier(i: &mut Tokens<'_, Kind>) -> PResult<AltIdentifier> {
194 let token = one_of_kinds([Kind::AltIdentifier, Kind::String]).parse_next(i)?;
195 let text = if token.kind == Kind::String {
196 inner(token, '"')?
197 } else {
198 token.text
199 };
200 let Some((scheme, code)) = text.split_once('#') else {
201 return backtrack();
202 };
203 if !is_scheme_alias(scheme) || code.is_empty() || code.contains('\\') {
204 return backtrack();
205 }
206 let term = opt(term).parse_next(i)?;
207 Ok(AltIdentifier {
208 scheme: scheme.to_owned(),
209 code: code.to_owned(),
210 term,
211 })
212}
213
214fn focus(i: &mut Tokens<'_, Kind>) -> PResult<FocusConcept> {
216 alt((
217 kind(Kind::Asterisk).value(FocusConcept::Wildcard),
218 concept_reference.map(FocusConcept::Reference),
219 alt_identifier.map(FocusConcept::AltIdentifier),
220 delimited(
221 kind(Kind::LeftParen),
222 expression_constraint,
223 kind(Kind::RightParen),
224 )
225 .map(|inner| FocusConcept::Nested(Box::new(inner))),
226 ))
227 .expecting("a concept reference, '*', an alternate identifier, or '('")
228 .parse_next(i)
229}
230
231fn constraint_operator(i: &mut Tokens<'_, Kind>) -> PResult<ConstraintOperator> {
233 any.verify_map(|t: &Token<'_, Kind>| match t.kind {
234 Kind::LessThan => Some(ConstraintOperator::DescendantOf),
235 Kind::DescendantOrSelfOf => Some(ConstraintOperator::DescendantOrSelfOf),
236 Kind::ChildOf => Some(ConstraintOperator::ChildOf),
237 Kind::ChildOrSelfOf => Some(ConstraintOperator::ChildOrSelfOf),
238 Kind::GreaterThan => Some(ConstraintOperator::AncestorOf),
239 Kind::AncestorOrSelfOf => Some(ConstraintOperator::AncestorOrSelfOf),
240 Kind::ParentOf => Some(ConstraintOperator::ParentOf),
241 Kind::ParentOrSelfOf => Some(ConstraintOperator::ParentOrSelfOf),
242 Kind::Top => Some(ConstraintOperator::Top),
243 Kind::Bottom => Some(ConstraintOperator::Bottom),
244 _ => None,
245 })
246 .expecting("a constraint operator")
247 .parse_next(i)
248}
249
250fn refset_field_name(i: &mut Tokens<'_, Kind>) -> PResult<String> {
252 kind(Kind::Identifier)
253 .verify(|t: &Token<'_, Kind>| t.text.bytes().all(|b| b.is_ascii_alphabetic()))
254 .map(|t| t.text.to_owned())
255 .expecting("a reference set field name")
256 .parse_next(i)
257}
258
259fn refset_operator(i: &mut Tokens<'_, Kind>) -> PResult<(RefsetOperator, Option<FocusConcept>)> {
264 let caret = kind(Kind::Caret).parse_next(i)?;
265 let start = i.checkpoint();
266 if let Some(word) = i
267 .peek_token()
268 .filter(|t| t.kind == Kind::Identifier && adjacent(caret, t))
269 && let Some(rest) = word.text.strip_prefix('R')
270 && rest.bytes().all(|b| b.is_ascii_digit())
271 {
272 any.void().parse_next(i)?;
273 if rest.is_empty() {
274 return Ok((RefsetOperator::ContainingAny, None));
275 }
276 let Some(id) = sctid_text(rest) else {
277 return refuse(i, &start, "a SNOMED CT identifier");
278 };
279 let term = opt(term).parse_next(i)?;
280 let focus = FocusConcept::Reference(ConceptReference { id, term });
281 return Ok((RefsetOperator::ContainingAny, Some(focus)));
282 }
283 member_of_fields
284 .map(|member_of| (RefsetOperator::MemberOf(member_of), None))
285 .parse_next(i)
286}
287
288fn member_of_fields(i: &mut Tokens<'_, Kind>) -> PResult<MemberOf> {
290 let fields = opt(delimited(
291 kind(Kind::LeftBracket),
292 cut_err(alt((
293 kind(Kind::Asterisk).value(RefsetFields::Any),
294 separated(1.., refset_field_name, kind(Kind::Comma)).map(RefsetFields::Names),
295 ))),
296 cut_err(kind(Kind::RightBracket)),
297 ))
298 .parse_next(i)?;
299 Ok(MemberOf { fields })
300}
301
302fn sub_expression_constraint(i: &mut Tokens<'_, Kind>) -> PResult<SubExpressionConstraint> {
304 let operator = opt(constraint_operator).parse_next(i)?;
305 let (refset_operator, glued) = match opt(refset_operator).parse_next(i)? {
306 Some((refset_operator, glued)) => (Some(refset_operator), glued),
307 None => (None, None),
308 };
309 let focus = match glued {
310 Some(focus) => focus,
311 None if operator.is_some() || refset_operator.is_some() => cut_err(focus).parse_next(i)?,
312 None => focus.parse_next(i)?,
313 };
314 sub_expression_rest(i, operator, refset_operator, focus)
315}
316
317fn sub_expression_rest(
319 i: &mut Tokens<'_, Kind>,
320 operator: Option<ConstraintOperator>,
321 refset_operator: Option<RefsetOperator>,
322 focus: FocusConcept,
323) -> PResult<SubExpressionConstraint> {
324 let member_filters = repeat(0.., member_filter_constraint).parse_next(i)?;
325 let filters = repeat(
326 0..,
327 alt((concept_filter_constraint, description_filter_constraint)),
328 )
329 .parse_next(i)?;
330 let history = opt(history_supplement).parse_next(i)?;
331 Ok(SubExpressionConstraint {
332 operator,
333 refset_operator,
334 focus,
335 member_filters,
336 filters,
337 history,
338 })
339}
340
341fn operands<'i>(
344 first: SubExpressionConstraint,
345 junction: impl Parser<Tokens<'i, Kind>, (), ErrMode<Failure>>,
346 i: &mut Tokens<'i, Kind>,
347) -> PResult<Option<Vec<SubExpressionConstraint>>> {
348 let rest: Option<Vec<SubExpressionConstraint>> = opt(repeat(
349 1..,
350 preceded(junction, cut_err(sub_expression_constraint)),
351 ))
352 .parse_next(i)?;
353 Ok(rest.map(|rest| {
354 let mut all = vec![first];
355 all.extend(rest);
356 all
357 }))
358}
359
360fn expression_constraint(i: &mut Tokens<'_, Kind>) -> PResult<ExpressionConstraint> {
363 let first = sub_expression_constraint.parse_next(i)?;
364 if opt(kind(Kind::Colon)).parse_next(i)?.is_some() {
365 let refinement = cut_err(refinement).parse_next(i)?;
366 return Ok(ExpressionConstraint::Refined {
367 focus: first,
368 refinement: Box::new(refinement),
369 });
370 }
371 if i.peek_token().is_some_and(|t| t.kind == Kind::Period) {
372 let attributes = repeat(
373 1..,
374 preceded(kind(Kind::Period), cut_err(sub_expression_constraint)),
375 )
376 .parse_next(i)?;
377 return Ok(ExpressionConstraint::Dotted {
378 focus: first,
379 attributes,
380 });
381 }
382 if let Some(all) = operands(first.clone(), conjunction, i)? {
383 return Ok(ExpressionConstraint::Conjunction(all));
384 }
385 if let Some(all) = operands(first.clone(), disjunction, i)? {
386 return Ok(ExpressionConstraint::Disjunction(all));
387 }
388 if let Some(right) =
389 opt(preceded(exclusion, cut_err(sub_expression_constraint))).parse_next(i)?
390 {
391 return Ok(ExpressionConstraint::Exclusion { left: first, right });
392 }
393 Ok(ExpressionConstraint::Sub(first))
394}
395
396fn non_negative_integer(token: &Token<'_, Kind>) -> Option<u32> {
398 (token.text == "0" || !token.text.starts_with('0'))
399 .then(|| token.text.parse().ok())
400 .flatten()
401}
402
403fn cardinality(i: &mut Tokens<'_, Kind>) -> PResult<Cardinality> {
405 let start = i.checkpoint();
406 let open = kind(Kind::LeftBracket).parse_next(i)?;
407 let (min, to, max, close) = cut_err(
408 (
409 kind(Kind::Integer),
410 kind(Kind::To),
411 one_of_kinds([Kind::Integer, Kind::Asterisk]),
412 kind(Kind::RightBracket),
413 )
414 .expecting("a cardinality such as [1..*]"),
415 )
416 .parse_next(i)?;
417 let tight =
418 adjacent(open, min) && adjacent(min, to) && adjacent(to, max) && adjacent(max, close);
419 let min_value = non_negative_integer(min);
420 let max_value = if max.kind == Kind::Asterisk {
421 Some(None)
422 } else {
423 non_negative_integer(max).map(Some)
424 };
425 match (tight, min_value, max_value) {
426 (true, Some(min), Some(max)) => Ok(Cardinality { min, max }),
427 _ => refuse(i, &start, "a cardinality such as [1..*]"),
428 }
429}
430
431fn equality(i: &mut Tokens<'_, Kind>) -> PResult<Equality> {
433 alt((
434 kind(Kind::Equal).value(Equality::Equal),
435 kind(Kind::NotEqual).value(Equality::NotEqual),
436 ))
437 .parse_next(i)
438}
439
440fn comparison(i: &mut Tokens<'_, Kind>) -> PResult<Comparison> {
442 alt((
443 kind(Kind::Equal).value(Comparison::Equal),
444 kind(Kind::NotEqual).value(Comparison::NotEqual),
445 kind(Kind::LessOrEqual).value(Comparison::LessOrEqual),
446 kind(Kind::LessThan).value(Comparison::Less),
447 kind(Kind::GreaterOrEqual).value(Comparison::GreaterOrEqual),
448 kind(Kind::GreaterThan).value(Comparison::Greater),
449 ))
450 .parse_next(i)
451}
452
453fn numeric_value(i: &mut Tokens<'_, Kind>) -> PResult<NumericValue> {
456 let start = i.checkpoint();
457 let hash = kind(Kind::Hash).parse_next(i)?;
458 let sign = opt(one_of_kinds([Kind::Dash, Kind::Plus])).parse_next(i)?;
459 let integer = cut_err(kind(Kind::Integer)).parse_next(i)?;
460 let fraction = opt((kind(Kind::Period), kind(Kind::Integer))).parse_next(i)?;
461 let mut previous = hash;
462 let mut text = String::new();
463 for token in sign
464 .into_iter()
465 .chain(std::iter::once(integer))
466 .chain(fraction.iter().flat_map(|(dot, digits)| [*dot, *digits]))
467 {
468 if !adjacent(previous, token) {
469 return refuse(i, &start, "a number written without spaces");
470 }
471 text.push_str(token.text);
472 previous = token;
473 }
474 if integer.text != "0" && integer.text.starts_with('0') {
475 return refuse(i, &start, "a number without a leading zero");
476 }
477 Ok(NumericValue(text))
478}
479
480fn boolean(i: &mut Tokens<'_, Kind>) -> PResult<bool> {
482 alt((keyword("true").value(true), keyword("false").value(false))).parse_next(i)
483}
484
485fn string_content<'i>(i: &mut Tokens<'i, Kind>) -> PResult<&'i str> {
487 let token = kind(Kind::String).parse_next(i)?;
488 inner(token, '"')
489}
490
491fn escapes_only(text: &str, allowed: &[char]) -> bool {
493 let mut chars = text.chars();
494 while let Some(c) = chars.next() {
495 if c == '\\' && !chars.next().is_some_and(|e| allowed.contains(&e)) {
496 return false;
497 }
498 }
499 true
500}
501
502fn match_words(text: &str) -> Option<Vec<String>> {
505 let mut words = Vec::new();
506 let mut word = String::new();
507 let mut rest = text;
508 while let Some(c) = rest.chars().next() {
509 if let Some(after) = rest.strip_prefix("/*") {
510 let end = after.find("*/")?;
511 rest = after.get(end.saturating_add(2)..)?;
512 if !word.is_empty() {
513 words.push(std::mem::take(&mut word));
514 }
515 continue;
516 }
517 rest = rest.get(c.len_utf8()..)?;
518 if matches!(c, ' ' | '\t' | '\r' | '\n') {
519 if !word.is_empty() {
520 words.push(std::mem::take(&mut word));
521 }
522 } else if c == '\\' {
523 let escaped = rest.chars().next().filter(|e| matches!(e, '"' | '\\'))?;
524 rest = rest.get(escaped.len_utf8()..)?;
525 word.push('\\');
526 word.push(escaped);
527 } else {
528 word.push(c);
529 }
530 }
531 if !word.is_empty() {
532 words.push(word);
533 }
534 (!words.is_empty()).then_some(words)
535}
536
537fn typed_search_term(i: &mut Tokens<'_, Kind>) -> PResult<TypedSearchTerm> {
539 if opt((keyword("wild"), kind(Kind::Colon)))
540 .parse_next(i)?
541 .is_some()
542 {
543 let start = i.checkpoint();
544 let pattern = cut_err(string_content).parse_next(i)?;
545 if pattern.is_empty() || !escapes_only(pattern, &['"', '\\', '*']) {
546 return refuse(i, &start, "a wildcard search term");
547 }
548 return Ok(TypedSearchTerm::Wild(pattern.to_owned()));
549 }
550 let explicit = opt((keyword("match"), kind(Kind::Colon)))
551 .parse_next(i)?
552 .is_some();
553 let start = i.checkpoint();
554 let content = if explicit {
555 cut_err(string_content).parse_next(i)?
556 } else {
557 string_content.parse_next(i)?
558 };
559 match match_words(content) {
560 Some(words) => Ok(TypedSearchTerm::Match(words)),
561 None => refuse(i, &start, "one or more search words"),
562 }
563}
564
565fn concrete_string(i: &mut Tokens<'_, Kind>) -> PResult<ConcreteString> {
568 let start = i.checkpoint();
569 let content = string_content.parse_next(i)?;
570 if content.is_empty() || !escapes_only(content, &['"', '\\', '*']) {
571 return refuse(i, &start, "a concrete string");
572 }
573 let mut value = String::with_capacity(content.len());
574 let mut chars = content.chars();
575 while let Some(c) = chars.next() {
576 match c {
577 '\\' => value.extend(chars.next()),
578 other => value.push(other),
579 }
580 }
581 Ok(ConcreteString(value))
582}
583
584fn concrete_strings(i: &mut Tokens<'_, Kind>) -> PResult<Vec<ConcreteString>> {
586 bare_or_set(concrete_string, "a concrete string").parse_next(i)
587}
588
589fn typed_search_terms(i: &mut Tokens<'_, Kind>) -> PResult<Vec<TypedSearchTerm>> {
591 bare_or_set(typed_search_term, "a search term").parse_next(i)
592}
593
594fn history_supplement(i: &mut Tokens<'_, Kind>) -> PResult<HistorySupplement> {
596 (kind(Kind::DoubleLeftBrace), kind(Kind::Plus)).parse_next(i)?;
597 let start = i.checkpoint();
598 let word = cut_err(kind(Kind::Identifier).expecting("HISTORY")).parse_next(i)?;
599 let profile = match word.text.to_ascii_uppercase().as_str() {
600 "HISTORY" => None,
601 "HISTORY-MIN" | "HISTORY_MIN" => Some(HistorySupplement::Minimum),
602 "HISTORY-MOD" | "HISTORY_MOD" => Some(HistorySupplement::Moderate),
603 "HISTORY-MAX" | "HISTORY_MAX" => Some(HistorySupplement::Maximum),
604 _ => {
605 return refuse(
606 i,
607 &start,
608 "HISTORY, HISTORY-MIN, HISTORY-MOD, or HISTORY-MAX",
609 );
610 }
611 };
612 let supplement = match profile {
613 Some(profile) => profile,
614 None => match opt(delimited(
615 kind(Kind::LeftParen),
616 cut_err(expression_constraint),
617 cut_err(kind(Kind::RightParen)),
618 ))
619 .parse_next(i)?
620 {
621 Some(subset) => HistorySupplement::Subset(Box::new(subset)),
622 None => HistorySupplement::Default,
623 },
624 };
625 cut_err(kind(Kind::DoubleRightBrace)).parse_next(i)?;
626 Ok(supplement)
627}
628
629fn attribute(i: &mut Tokens<'_, Kind>) -> PResult<Attribute> {
632 let cardinality = opt(cardinality).parse_next(i)?;
633 let (reverse, name) = if let Some(name) = glued_reverse(i)? {
634 (true, name)
635 } else if opt(keyword("R")).parse_next(i)?.is_some() {
636 (true, cut_err(sub_expression_constraint).parse_next(i)?)
637 } else {
638 (false, sub_expression_constraint.parse_next(i)?)
639 };
640 let value = cut_err(attribute_value).parse_next(i)?;
641 Ok(Attribute {
642 cardinality,
643 reverse,
644 name,
645 value,
646 })
647}
648
649fn glued_reverse(i: &mut Tokens<'_, Kind>) -> PResult<Option<SubExpressionConstraint>> {
653 let Some(id) = i.peek_token().and_then(|t| {
654 let rest = t.text.strip_prefix(['R', 'r'])?;
655 (t.kind == Kind::Identifier && rest.bytes().all(|b| b.is_ascii_digit()))
656 .then(|| sctid_text(rest))
657 .flatten()
658 }) else {
659 return Ok(None);
660 };
661 any.void().parse_next(i)?;
662 let term = opt(term).parse_next(i)?;
663 let focus = FocusConcept::Reference(ConceptReference { id, term });
664 sub_expression_rest(i, None, None, focus).map(Some)
665}
666
667fn attribute_value(i: &mut Tokens<'_, Kind>) -> PResult<AttributeValue> {
669 alt((
670 (equality, sub_expression_constraint)
671 .map(|(operator, value)| AttributeValue::Expression { operator, value }),
672 (comparison, numeric_value)
673 .map(|(operator, value)| AttributeValue::Numeric { operator, value }),
674 (equality, concrete_strings)
675 .map(|(operator, values)| AttributeValue::String { operator, values }),
676 (equality, boolean).map(|(operator, value)| AttributeValue::Boolean { operator, value }),
677 preceded(
678 alt((equality.void(), comparison.void())),
679 cut_err(fail.expecting("an attribute value")),
680 ),
681 ))
682 .expecting("a comparison operator")
683 .parse_next(i)
684}
685
686fn sub_attribute_set(i: &mut Tokens<'_, Kind>) -> PResult<SubAttributeSet> {
688 alt((
689 attribute.map(|attribute| SubAttributeSet::Attribute(Box::new(attribute))),
690 delimited(kind(Kind::LeftParen), attribute_set, kind(Kind::RightParen))
691 .map(|set| SubAttributeSet::Nested(Box::new(set))),
692 ))
693 .parse_next(i)
694}
695
696fn joined<'i, T: Clone>(
698 first: &T,
699 junction: impl Parser<Tokens<'i, Kind>, (), ErrMode<Failure>>,
700 item: impl Parser<Tokens<'i, Kind>, T, ErrMode<Failure>>,
701 i: &mut Tokens<'i, Kind>,
702) -> PResult<Option<Vec<T>>> {
703 let rest: Option<Vec<T>> = opt(repeat(1.., preceded(junction, item))).parse_next(i)?;
704 Ok(rest.map(|rest| {
705 let mut all = vec![first.clone()];
706 all.extend(rest);
707 all
708 }))
709}
710
711fn attribute_set(i: &mut Tokens<'_, Kind>) -> PResult<AttributeSet> {
713 let first = sub_attribute_set.parse_next(i)?;
714 if let Some(all) = joined(&first, conjunction, sub_attribute_set, i)? {
715 return Ok(AttributeSet::Conjunction(all));
716 }
717 if let Some(all) = joined(&first, disjunction, sub_attribute_set, i)? {
718 return Ok(AttributeSet::Disjunction(all));
719 }
720 Ok(AttributeSet::Single(Box::new(first)))
721}
722
723fn attribute_group(i: &mut Tokens<'_, Kind>) -> PResult<SubRefinement> {
725 let cardinality = opt(cardinality).parse_next(i)?;
726 kind(Kind::LeftBrace).parse_next(i)?;
727 let attributes = cut_err(attribute_set).parse_next(i)?;
728 cut_err(kind(Kind::RightBrace)).parse_next(i)?;
729 Ok(SubRefinement::Group {
730 cardinality,
731 attributes,
732 })
733}
734
735fn sub_refinement(i: &mut Tokens<'_, Kind>) -> PResult<SubRefinement> {
738 alt((
739 attribute_set.map(SubRefinement::AttributeSet),
740 attribute_group,
741 delimited(
742 kind(Kind::LeftParen),
743 cut_err(refinement),
744 cut_err(kind(Kind::RightParen)),
745 )
746 .map(|inner| SubRefinement::Nested(Box::new(inner))),
747 ))
748 .expecting("an attribute, an attribute group, or '('")
749 .parse_next(i)
750}
751
752fn refinement(i: &mut Tokens<'_, Kind>) -> PResult<Refinement> {
754 let first = sub_refinement.parse_next(i)?;
755 if let Some(all) = joined(&first, conjunction, cut_err(sub_refinement), i)? {
756 return Ok(Refinement::Conjunction(all));
757 }
758 if let Some(all) = joined(&first, disjunction, cut_err(sub_refinement), i)? {
759 return Ok(Refinement::Disjunction(all));
760 }
761 Ok(Refinement::Single(Box::new(first)))
762}
763
764pub fn whole(i: &mut Tokens<'_, Kind>) -> PResult<ExpressionConstraint> {
771 let constraint = expression_constraint
772 .expecting("an expression constraint")
773 .parse_next(i)?;
774 eof.expecting("the end of the expression").parse_next(i)?;
775 Ok(constraint)
776}