Skip to main content

ferrobridge_fhirpath/
parse.rs

1// SPDX-FileCopyrightText: Cadasto B.V.
2// SPDX-License-Identifier: BUSL-1.1
3
4//! The parser of the N1 grammar.
5//!
6//! It reads the tokens of the private `lex` module by the grammar of
7//! <https://hl7.org/fhirpath/N1/grammar.html>, with the operator precedence
8//! of N1 §6.8 and every binary operator associating to the left.
9
10use std::ops::Range;
11
12use chumsky::prelude::*;
13use rust_decimal::Decimal;
14
15use crate::ast::{BinaryOp, Expr, Literal, TypeSpecifier};
16use crate::lex::{Token, lex};
17use crate::quantity::Unit;
18use crate::temporal::parse::{Temporal, literal};
19
20/// Why a text is no FHIRPath expression.
21#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
22pub enum ParseError {
23    /// Text no token of the grammar matches, such as an unterminated string
24    /// or comment.
25    #[error("no token of the grammar matches {text:?} at byte {offset}")]
26    Lex {
27        /// The byte offset.
28        offset: usize,
29        /// The text no token matches.
30        text: String,
31    },
32    /// Tokens the grammar does not admit at that place.
33    #[error("the expression leaves the grammar at byte {offset}")]
34    Syntax {
35        /// The byte offset of the first token not admitted, the length of the
36        /// text when the expression ends too early.
37        offset: usize,
38    },
39    /// A literal the grammar admits but whose value does not exist, such as
40    /// `@2015-02-30` or an integer past 2^31-1.
41    #[error("the literal {text:?} at byte {offset} names no value")]
42    Literal {
43        /// The byte offset.
44        offset: usize,
45        /// The literal as written.
46        text: String,
47    },
48}
49
50/// The chumsky extra parameter of this grammar.
51#[expect(
52    unused_qualifications,
53    reason = "the alias shadows the name it qualifies, so the path keeps the definition from naming itself"
54)]
55type Extra<'t> = chumsky::extra::Err<Simple<'t, Token>>;
56
57/// Parses `text` into a syntax tree.
58///
59/// # Errors
60///
61/// Returns [`ParseError::Lex`] for text no token matches,
62/// [`ParseError::Syntax`] for tokens outside the grammar and
63/// [`ParseError::Literal`] for a literal that names no value.
64pub fn parse(text: &str) -> Result<Expr, ParseError> {
65    let spanned = lex(text)?;
66    for (token, span) in &spanned {
67        check_literal(token, span)?;
68    }
69    let tokens: Vec<Token> = spanned.iter().map(|(token, _)| token.clone()).collect();
70    grammar()
71        .parse(tokens.as_slice())
72        .into_result()
73        .map_err(|errors| {
74            let index = errors
75                .first()
76                .map_or(tokens.len(), |error| error.span().start);
77            ParseError::Syntax {
78                offset: spanned
79                    .get(index)
80                    .map_or(text.len(), |(_, span)| span.start),
81            }
82        })
83}
84
85/// Refuses a number or a date and time literal that names no value.
86fn check_literal(token: &Token, span: &Range<usize>) -> Result<(), ParseError> {
87    let refuse = |text: &str| ParseError::Literal {
88        offset: span.start,
89        text: String::from(text),
90    };
91    match token {
92        Token::Temporal(text) if literal(text).is_none() => Err(refuse(text)),
93        Token::Number(text) if number(text).is_none() => Err(refuse(text)),
94        _ => Ok(()),
95    }
96}
97
98/// The literal a number token stands for: an integer within 32 bits (N1
99/// §4.1.3), or a decimal.
100fn number(text: &str) -> Option<Literal> {
101    if text.contains('.') {
102        text.parse::<Decimal>().ok().map(Literal::Decimal)
103    } else {
104        text.parse::<i32>()
105            .ok()
106            .map(|value| Literal::Integer(i64::from(value)))
107    }
108}
109
110/// The literal a date or time token stands for.
111fn temporal(text: &str) -> Option<Literal> {
112    Some(match literal(text)? {
113        Temporal::Date(value) => Literal::Date(value),
114        Temporal::DateTime(value) => Literal::DateTime(value),
115        Temporal::Time(value) => Literal::Time(value),
116    })
117}
118
119/// A left-associative level of binary operators over `operand`.
120fn level<'t>(
121    operand: impl Parser<'t, &'t [Token], Expr, Extra<'t>> + Clone + 't,
122    operators: impl Parser<'t, &'t [Token], BinaryOp, Extra<'t>> + Clone + 't,
123) -> impl Parser<'t, &'t [Token], Expr, Extra<'t>> + Clone {
124    operand
125        .clone()
126        .foldl(operators.then(operand).repeated(), |left, (op, right)| {
127            Expr::Binary {
128                op,
129                left: Box::new(left),
130                right: Box::new(right),
131            }
132        })
133        .boxed()
134}
135
136/// What follows a term: an invocation after `.`, or an indexer.
137enum Postfix {
138    /// `.invocation`.
139    Invoke(Expr),
140    /// `[index]`.
141    Index(Expr),
142}
143
144/// `identifier`: a simple or delimited identifier, or one of the four
145/// keywords the grammar admits as one.
146fn identifier<'t>() -> impl Parser<'t, &'t [Token], String, Extra<'t>> + Clone {
147    select! {
148        Token::Identifier(name) => name,
149        Token::Delimited(name) => name,
150        Token::As => String::from("as"),
151        Token::Contains => String::from("contains"),
152        Token::In => String::from("in"),
153        Token::Is => String::from("is"),
154    }
155}
156
157/// `typeSpecifier`: a type name with an optional model qualifier.
158fn type_specifier<'t>() -> impl Parser<'t, &'t [Token], TypeSpecifier, Extra<'t>> + Clone {
159    identifier()
160        .then(just(Token::Dot).ignore_then(identifier()).or_not())
161        .map(|(first, second)| match second {
162            Some(name) => TypeSpecifier {
163                namespace: Some(first),
164                name,
165            },
166            None => TypeSpecifier {
167                namespace: None,
168                name: first,
169            },
170        })
171}
172
173/// `literal`, quantities included.
174fn literal_term<'t>() -> impl Parser<'t, &'t [Token], Expr, Extra<'t>> + Clone {
175    let unit = select! {
176        Token::Text(code) => Unit::from_code(code),
177        Token::Calendar(calendar) => Unit::Calendar(calendar),
178    };
179    let numeric = select! { Token::Number(text) => text }
180        .then(unit.or_not())
181        .try_map(|(text, unit), span| match (number(&text), unit) {
182            (Some(Literal::Integer(value)), Some(unit)) => {
183                Ok(Literal::Quantity(Decimal::from(value), unit))
184            }
185            (Some(Literal::Decimal(value)), Some(unit)) => Ok(Literal::Quantity(value, unit)),
186            (Some(literal), None) => Ok(literal),
187            _ => Err(Simple::new(None, span)),
188        });
189    choice((
190        just(Token::OpenBrace)
191            .then(just(Token::CloseBrace))
192            .to(Expr::Empty),
193        just(Token::True).to(Expr::Literal(Literal::Boolean(true))),
194        just(Token::False).to(Expr::Literal(Literal::Boolean(false))),
195        select! { Token::Text(text) => Expr::Literal(Literal::String(text)) },
196        select! { Token::Temporal(text) => text }.try_map(|text, span| {
197            temporal(&text)
198                .map(Expr::Literal)
199                .ok_or_else(|| Simple::new(None, span))
200        }),
201        numeric.map(Expr::Literal),
202    ))
203}
204
205/// `invocation`: a member, a function call, or a special variable.
206fn invocation<'t>(
207    expression: impl Parser<'t, &'t [Token], Expr, Extra<'t>> + Clone + 't,
208) -> impl Parser<'t, &'t [Token], Expr, Extra<'t>> + Clone {
209    let arguments = expression
210        .clone()
211        .separated_by(just(Token::Comma))
212        .collect::<Vec<Expr>>()
213        .delimited_by(just(Token::OpenParen), just(Token::CloseParen));
214    let direction = select! {
215        Token::Identifier(word) if word == "asc" => false,
216        Token::Identifier(word) if word == "desc" => true,
217    };
218    let sort_arguments = expression
219        .then(direction.or_not())
220        .map(|(key, direction)| match direction {
221            Some(descending) => Expr::SortKey {
222                key: Box::new(key),
223                descending,
224            },
225            None => key,
226        })
227        .separated_by(just(Token::Comma))
228        .collect::<Vec<Expr>>()
229        .delimited_by(just(Token::OpenParen), just(Token::CloseParen));
230    choice((
231        // NOTE: the FHIRPath 3.0.0 grammar gives sort() its own rule, `'sort' '('
232        // (sortArgument (',' sortArgument)*)? ')'`, where a key may carry asc or desc.
233        select! { Token::Identifier(name) if name == "sort" => name }
234            .then(sort_arguments)
235            .map(|(name, args)| Expr::Call {
236                input: None,
237                name,
238                args,
239            }),
240        identifier()
241            .then(arguments.or_not())
242            .map(|(name, args)| match args {
243                Some(args) => Expr::Call {
244                    input: None,
245                    name,
246                    args,
247                },
248                None => Expr::Member { input: None, name },
249            }),
250        just(Token::This).to(Expr::This),
251        just(Token::Index).to(Expr::Index),
252        just(Token::Total).to(Expr::Total),
253    ))
254}
255
256/// A term followed by its invocations and indexers, the tightest-binding
257/// operators of N1 §6.8.
258fn invoked<'t>(
259    expression: impl Parser<'t, &'t [Token], Expr, Extra<'t>> + Clone + 't,
260) -> impl Parser<'t, &'t [Token], Expr, Extra<'t>> + Clone {
261    let external = just(Token::Percent)
262        .ignore_then(choice((
263            identifier(),
264            select! { Token::Text(text) => text },
265        )))
266        .map(Expr::External);
267    let term = choice((
268        literal_term(),
269        external,
270        invocation(expression.clone()),
271        expression
272            .clone()
273            .delimited_by(just(Token::OpenParen), just(Token::CloseParen)),
274    ));
275    let postfix = choice((
276        just(Token::Dot)
277            .ignore_then(invocation(expression.clone()))
278            .map(Postfix::Invoke),
279        expression
280            .delimited_by(just(Token::OpenBracket), just(Token::CloseBracket))
281            .map(Postfix::Index),
282    ));
283    term.foldl(postfix.repeated(), |input, postfix| match postfix {
284        Postfix::Invoke(Expr::Member { name, .. }) => Expr::Member {
285            input: Some(Box::new(input)),
286            name,
287        },
288        Postfix::Invoke(Expr::Call { name, args, .. }) => Expr::Call {
289            input: Some(Box::new(input)),
290            name,
291            args,
292        },
293        Postfix::Invoke(variable) => Expr::Invocation {
294            input: Box::new(input),
295            variable: Box::new(variable),
296        },
297        Postfix::Index(index) => Expr::Indexer {
298            input: Box::new(input),
299            index: Box::new(index),
300        },
301    })
302    .boxed()
303}
304
305/// The unary and the arithmetic operators over `invoked`, up to the type
306/// operators (N1 §6.8, #03 to #06).
307fn arithmetic<'t>(
308    invoked: impl Parser<'t, &'t [Token], Expr, Extra<'t>> + Clone + 't,
309) -> impl Parser<'t, &'t [Token], Expr, Extra<'t>> + Clone {
310    let polarity = choice((just(Token::Plus).to(true), just(Token::Minus).to(false)))
311        .repeated()
312        .foldr(invoked, |plus, operand| {
313            if plus {
314                Expr::Plus(Box::new(operand))
315            } else {
316                Expr::Negate(Box::new(operand))
317            }
318        })
319        .boxed();
320    let multiplicative = level(
321        polarity,
322        choice((
323            just(Token::Star).to(BinaryOp::Multiply),
324            just(Token::Slash).to(BinaryOp::Divide),
325            just(Token::Div).to(BinaryOp::Div),
326            just(Token::Mod).to(BinaryOp::Mod),
327        )),
328    );
329    let additive = level(
330        multiplicative,
331        choice((
332            just(Token::Plus).to(BinaryOp::Add),
333            just(Token::Minus).to(BinaryOp::Subtract),
334            just(Token::Ampersand).to(BinaryOp::Concatenate),
335        )),
336    );
337    additive
338        .foldl(
339            choice((just(Token::Is).to(true), just(Token::As).to(false)))
340                .then(type_specifier())
341                .repeated(),
342            |operand, (is, type_specifier)| {
343                let operand = Box::new(operand);
344                if is {
345                    Expr::Is {
346                        operand,
347                        type_specifier,
348                    }
349                } else {
350                    Expr::As {
351                        operand,
352                        type_specifier,
353                    }
354                }
355            },
356        )
357        .boxed()
358}
359
360/// The collection, comparison and Boolean operators over `typed` (N1 §6.8,
361/// #07 to #13).
362fn logical<'t>(
363    typed: impl Parser<'t, &'t [Token], Expr, Extra<'t>> + Clone + 't,
364) -> impl Parser<'t, &'t [Token], Expr, Extra<'t>> + Clone {
365    let union = level(typed, just(Token::Pipe).to(BinaryOp::Union));
366    let inequality = level(
367        union,
368        choice((
369            just(Token::LessOrEqual).to(BinaryOp::LessOrEqual),
370            just(Token::Less).to(BinaryOp::Less),
371            just(Token::GreaterOrEqual).to(BinaryOp::GreaterOrEqual),
372            just(Token::Greater).to(BinaryOp::Greater),
373        )),
374    );
375    let equality = level(
376        inequality,
377        choice((
378            just(Token::Equal).to(BinaryOp::Equal),
379            just(Token::Tilde).to(BinaryOp::Equivalent),
380            just(Token::NotEqual).to(BinaryOp::NotEqual),
381            just(Token::NotTilde).to(BinaryOp::NotEquivalent),
382        )),
383    );
384    let membership = level(
385        equality,
386        choice((
387            just(Token::In).to(BinaryOp::In),
388            just(Token::Contains).to(BinaryOp::Contains),
389        )),
390    );
391    let and = level(membership, just(Token::And).to(BinaryOp::And));
392    let or = level(
393        and,
394        choice((
395            just(Token::Or).to(BinaryOp::Or),
396            just(Token::Xor).to(BinaryOp::Xor),
397        )),
398    );
399    level(or, just(Token::Implies).to(BinaryOp::Implies))
400}
401
402/// The grammar.
403fn grammar<'t>() -> impl Parser<'t, &'t [Token], Expr, Extra<'t>> {
404    recursive(|expression| logical(arithmetic(invoked(expression)))).then_ignore(end())
405}