Skip to main content

sct_ecl/
parser.rs

1//! The parser: one function per rule of `ECL.g4`, over the token stream of
2//! [`crate::lexer`], with `winnow`.
3//!
4//! Every rule keeps the grammar's alternative order, so an input that two
5//! alternatives admit is read the way the reference grammar reads it. The
6//! grammar's mandatory whitespace (`mws`) and its adjacency (a number, a
7//! cardinality) are checked from the token spans, since the lexer skips
8//! whitespace.
9
10use 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
28/// The token stream.
29pub type Tokens<'i> = TokenSlice<'i, Token<'i>>;
30/// A parser result.
31type PResult<T> = ModalResult<T, Failure>;
32
33/// Why a rule did not match: the token class it expected, when a rule said.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
35pub struct Failure {
36    /// The token class expected where the parser stopped.
37    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
64/// Names what a parser expected when it backtracks; a cut error keeps the
65/// deeper rule's own expectation.
66trait 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
86/// Rewinds to `start` and refuses with `what`, so the error points at the
87/// token the rule read, not past it.
88fn 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
99/// One token of `kind`.
100fn kind<'i>(kind: Kind) -> impl Parser<Tokens<'i>, &'i Token<'i>, ErrMode<Failure>> {
101    one_of(kind).expecting(kind.describe())
102}
103
104/// A word, compared case-insensitively (every keyword of the grammar spells
105/// each letter as `(CAP_X | X)`).
106fn 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
111/// Whether whitespace or a comment separates two tokens (`mws`).
112fn separated_by_space(previous: &Token<'_>, next: &Token<'_>) -> bool {
113    previous.span.end < next.span.start
114}
115
116/// Whether two tokens touch (no whitespace between them).
117fn adjacent(previous: &Token<'_>, next: &Token<'_>) -> bool {
118    previous.span.end == next.span.start
119}
120
121/// A junction word that the grammar follows with mandatory whitespace
122/// (`conjunction`, `disjunction`, `exclusion`).
123fn 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
136/// `conjunction`: `AND` or `,`.
137fn conjunction(i: &mut Tokens<'_>) -> PResult<()> {
138    alt((junction_word("AND"), kind(Kind::Comma).void())).parse_next(i)
139}
140
141/// `disjunction`: `OR`.
142fn disjunction(i: &mut Tokens<'_>) -> PResult<()> {
143    junction_word("OR").parse_next(i)
144}
145
146/// `exclusion`: `MINUS`.
147fn exclusion(i: &mut Tokens<'_>) -> PResult<()> {
148    junction_word("MINUS").parse_next(i)
149}
150
151/// The text between a token's delimiters (the pipes of a term, the quotes
152/// of a string).
153fn 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
161/// `( item (mws item)* )`, at least `min` items, each separated from the
162/// previous one by whitespace.
163fn 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
199/// One item bare, or a set of them.
200fn 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
212/// `sctid`: 6 to 18 digits, the first not zero.
213fn 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
225/// `term`: the text between pipes, without its surrounding whitespace; one
226/// line, not empty.
227fn 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
237/// `eclconceptreference`.
238fn 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
244/// `altidentifierschemealias`: `alpha (dash | alpha | integervalue)*`.
245fn 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
251/// `altidentifier`: `scheme#code` bare or in quotes, with an optional term.
252fn 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
273/// `eclfocusconcept`, or the parenthesized nested constraint.
274fn 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
290/// `constraintoperator`.
291fn 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
309/// `refsetfieldname`: letters only.
310fn 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
318/// `memberof`: `^`, optionally `[ fields ]`.
319fn 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
333/// `subexpressionconstraint`.
334fn 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
359/// The operands after the first of a compound constraint, joined by
360/// `junction`.
361fn 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
378/// `expressionconstraint`: a sub-expression, then what follows it decides
379/// the form (`:` refined, `.` dotted, a junction compound, else bare).
380fn 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
414/// `nonnegativeintegervalue`: digits without a leading zero, or `0`.
415fn 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
421/// `cardinality` between brackets: `[min..max]`, written without spaces.
422fn 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
449/// `expressioncomparisonoperator` and the other `=` / `!=` operators.
450fn 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
458/// `numericcomparisonoperator` and `timecomparisonoperator`.
459fn 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
471/// `HASH numericvalue`: `#`, an optional sign, digits, an optional fraction,
472/// all touching.
473fn 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
498/// `booleanvalue`.
499fn boolean(i: &mut Tokens<'_>) -> PResult<bool> {
500    alt((keyword("true").value(true), keyword("false").value(false))).parse_next(i)
501}
502
503/// A quoted string's content.
504fn string_content<'i>(i: &mut Tokens<'i>) -> PResult<&'i str> {
505    let token = kind(Kind::String).parse_next(i)?;
506    inner(token, '"')
507}
508
509/// Whether every backslash in `text` escapes one of `allowed`.
510fn 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
520/// `matchsearchtermset` content: words separated by whitespace or comments,
521/// each with `\"` and `\\` as its only escapes.
522fn 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
555/// `typedsearchterm`: `wild:"pattern"`, or `[match:]"words"`.
556fn 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
583/// `typedsearchterm | typedsearchtermset`.
584fn typed_search_terms(i: &mut Tokens<'_>) -> PResult<Vec<TypedSearchTerm>> {
585    bare_or_set(typed_search_term, "a search term").parse_next(i)
586}
587
588/// `timevalue`: `""` or `"YYYYMMDD"`.
589fn 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
609/// `activevalue`: `1`, `0`, `true`, or `false`.
610fn 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
623/// `eclconceptreferenceset` (two or more) or a sub-expression constraint.
624///
625/// The grammar lists the constraint first; a set of two or more references
626/// never parses as one, so trying the set first reads every valid input the
627/// same way and keeps the constraint's errors for the invalid ones.
628fn 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
636/// `acceptabilityset`.
637fn 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
653/// `dialectidset`, kept only when it cannot also be read as a nested
654/// constraint (the grammar's first alternative for `( reference )`).
655fn 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
671/// `dialectalias`: a letter, then letters, digits, and dashes.
672fn 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
680/// `languagecode`: two letters.
681fn 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
691/// `typetoken`.
692fn 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
701/// `definitionstatustoken`.
702fn 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
710/// `dialectfilter`.
711fn 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
759/// `descriptionfilter`.
760fn 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
796/// `conceptfilter`.
797fn 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
826/// The value part of `memberfieldfilter`, in the grammar's alternative order.
827fn 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
847/// `memberfilter`.
848fn 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
866/// `{{ M filter, filter }}`.
867fn 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
878/// `{{ C filter, filter }}`.
879fn 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
891/// `{{ [D] filter, filter }}`; a `{{` that opens a member filter, a concept
892/// filter, or a history supplement is left to those rules.
893fn 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
912/// `historysupplement`.
913fn 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
947/// `eclattribute`. The cardinality is not committed to: a group starts the
948/// same way.
949fn 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
966/// The comparison and value of `eclattribute`, in the grammar's order.
967fn 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
985/// `subattributeset`.
986fn 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
995/// The items after the first of a junction-joined set.
996fn 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
1010/// `eclattributeset`.
1011fn 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
1022/// `eclattributegroup`.
1023fn 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
1034/// `subrefinement`, in the grammar's order: an attribute set, a group, a
1035/// parenthesized refinement.
1036fn 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
1051/// `eclrefinement`.
1052fn 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
1063/// A whole input: one expression constraint and nothing after it.
1064///
1065/// # Errors
1066///
1067/// Returns the failure at the first token the grammar does not admit, with
1068/// the token class expected there when a rule names one.
1069pub 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}