arithmetic_parser/parser/
mod.rs

1//! Parsers implemented with the help of `nom`.
2
3use nom::{
4    branch::alt,
5    bytes::{
6        complete::{tag, take_until, take_while, take_while1, take_while_m_n},
7        streaming,
8    },
9    character::complete::{char as tag_char, one_of},
10    combinator::{cut, map, map_res, not, opt, peek, recognize},
11    error::context,
12    multi::{many0, separated_list0, separated_list1},
13    sequence::{delimited, preceded, terminated, tuple},
14    Err as NomErr, Slice,
15};
16
17use core::mem;
18
19use crate::{
20    alloc::{vec, Box, Vec},
21    grammars::{Features, Grammar, Parse, ParseLiteral},
22    spans::{unite_spans, with_span},
23    BinaryOp, Block, Context, Destructure, DestructureRest, Error, ErrorKind, Expr, FnDefinition,
24    InputSpan, Lvalue, NomResult, ObjectDestructure, ObjectDestructureField, ObjectExpr, Spanned,
25    SpannedExpr, SpannedLvalue, SpannedStatement, Statement, UnaryOp,
26};
27
28#[cfg(test)]
29mod tests;
30
31trait GrammarType {
32    const COMPLETE: bool;
33}
34
35#[derive(Debug)]
36struct Complete(());
37
38impl GrammarType for Complete {
39    const COMPLETE: bool = true;
40}
41
42#[derive(Debug)]
43struct Streaming(());
44
45impl GrammarType for Streaming {
46    const COMPLETE: bool = false;
47}
48
49impl UnaryOp {
50    fn from_span(span: Spanned<'_, char>) -> Spanned<'_, Self> {
51        match span.extra {
52            '-' => span.copy_with_extra(UnaryOp::Neg),
53            '!' => span.copy_with_extra(UnaryOp::Not),
54            _ => unreachable!(),
55        }
56    }
57
58    fn try_from_byte(byte: u8) -> Option<Self> {
59        match byte {
60            b'-' => Some(Self::Neg),
61            b'!' => Some(Self::Not),
62            _ => None,
63        }
64    }
65}
66
67impl BinaryOp {
68    fn from_span(span: InputSpan<'_>) -> Spanned<'_, Self> {
69        Spanned::new(
70            span,
71            match *span.fragment() {
72                "+" => Self::Add,
73                "-" => Self::Sub,
74                "*" => Self::Mul,
75                "/" => Self::Div,
76                "^" => Self::Power,
77                "==" => Self::Eq,
78                "!=" => Self::NotEq,
79                "&&" => Self::And,
80                "||" => Self::Or,
81                ">" => Self::Gt,
82                "<" => Self::Lt,
83                ">=" => Self::Ge,
84                "<=" => Self::Le,
85                _ => unreachable!(),
86            },
87        )
88    }
89
90    fn is_supported(self, features: Features) -> bool {
91        match self {
92            Self::Add | Self::Sub | Self::Mul | Self::Div | Self::Power => true,
93            Self::Eq | Self::NotEq | Self::And | Self::Or => {
94                features.contains(Features::BOOLEAN_OPS_BASIC)
95            }
96            Self::Gt | Self::Lt | Self::Ge | Self::Le => features.contains(Features::BOOLEAN_OPS),
97        }
98    }
99}
100
101/// Whitespace and comments.
102fn ws<Ty: GrammarType>(input: InputSpan<'_>) -> NomResult<'_, InputSpan<'_>> {
103    fn narrow_ws<T: GrammarType>(input: InputSpan<'_>) -> NomResult<'_, InputSpan<'_>> {
104        if T::COMPLETE {
105            take_while1(|c: char| c.is_ascii_whitespace())(input)
106        } else {
107            streaming::take_while1(|c: char| c.is_ascii_whitespace())(input)
108        }
109    }
110
111    fn long_comment_body<T: GrammarType>(input: InputSpan<'_>) -> NomResult<'_, InputSpan<'_>> {
112        if T::COMPLETE {
113            context(Context::Comment.to_str(), cut(take_until("*/")))(input)
114        } else {
115            streaming::take_until("*/")(input)
116        }
117    }
118
119    let comment = preceded(tag("//"), take_while(|c: char| c != '\n'));
120    let long_comment = delimited(tag("/*"), long_comment_body::<Ty>, tag("*/"));
121    let ws_line = alt((narrow_ws::<Ty>, comment, long_comment));
122    recognize(many0(ws_line))(input)
123}
124
125fn mandatory_ws<Ty: GrammarType>(input: InputSpan<'_>) -> NomResult<'_, InputSpan<'_>> {
126    let not_ident_char = peek(not(take_while_m_n(1, 1, |c: char| {
127        c.is_ascii_alphanumeric() || c == '_'
128    })));
129    preceded(not_ident_char, ws::<Ty>)(input)
130}
131
132/// Variable name, like `a_foo` or `Bar`.
133fn var_name(input: InputSpan<'_>) -> NomResult<'_, InputSpan<'_>> {
134    context(
135        Context::Var.to_str(),
136        preceded(
137            peek(take_while_m_n(1, 1, |c: char| {
138                c.is_ascii_alphabetic() || c == '_'
139            })),
140            take_while1(|c: char| c.is_ascii_alphanumeric() || c == '_'),
141        ),
142    )(input)
143}
144
145/// Checks if the provided string is a valid variable name.
146pub fn is_valid_variable_name(name: &str) -> bool {
147    if name.is_empty() || !name.is_ascii() {
148        return false;
149    }
150
151    match var_name(InputSpan::new(name)) {
152        Ok((rest, _)) => rest.fragment().is_empty(),
153        Err(_) => false,
154    }
155}
156
157/// Function arguments in the call position; e.g., `(a, B + 1)`.
158///
159/// # Return value
160///
161/// The second component of the returned tuple is set to `true` if the list is `,`-terminated.
162fn fn_args<'a, T, Ty>(input: InputSpan<'a>) -> NomResult<'a, (Vec<SpannedExpr<'a, T::Base>>, bool)>
163where
164    T: Parse<'a>,
165    Ty: GrammarType,
166{
167    let maybe_comma = map(opt(preceded(ws::<Ty>, tag_char(','))), |c| c.is_some());
168
169    preceded(
170        terminated(tag_char('('), ws::<Ty>),
171        // Once we've encountered the opening `(`, the input *must* correspond to the parser.
172        cut(tuple((
173            separated_list0(delimited(ws::<Ty>, tag_char(','), ws::<Ty>), expr::<T, Ty>),
174            terminated(maybe_comma, tuple((ws::<Ty>, tag_char(')')))),
175        ))),
176    )(input)
177}
178
179/// Expression enclosed in parentheses. This may be a simple value (e.g., `(1 + 2)`)
180/// or a tuple (e.g., `(x, y)`), depending on the number of comma-separated terms.
181fn paren_expr<'a, T, Ty>(input: InputSpan<'a>) -> NomResult<'a, SpannedExpr<'a, T::Base>>
182where
183    T: Parse<'a>,
184    Ty: GrammarType,
185{
186    with_span(fn_args::<T, Ty>)(input).and_then(|(rest, parsed)| {
187        let comma_terminated = parsed.extra.1;
188        let terms = parsed.map_extra(|terms| terms.0);
189
190        match (terms.extra.len(), comma_terminated) {
191            (1, false) => Ok((
192                rest,
193                terms.map_extra(|mut terms| terms.pop().unwrap().extra),
194            )),
195
196            _ => {
197                if T::FEATURES.contains(Features::TUPLES) {
198                    Ok((rest, terms.map_extra(Expr::Tuple)))
199                } else {
200                    Err(NomErr::Failure(
201                        ErrorKind::UnexpectedTerm { context: None }.with_span(&terms),
202                    ))
203                }
204            }
205        }
206    })
207}
208
209/// Parses a block and wraps it into an `Expr`.
210fn block_expr<'a, T, Ty>(input: InputSpan<'a>) -> NomResult<'a, SpannedExpr<'a, T::Base>>
211where
212    T: Parse<'a>,
213    Ty: GrammarType,
214{
215    map(with_span(block::<T, Ty>), |spanned| {
216        spanned.map_extra(Expr::Block)
217    })(input)
218}
219
220fn object_expr_field<'a, T, Ty>(
221    input: InputSpan<'a>,
222) -> NomResult<'a, (Spanned<'a>, Option<SpannedExpr<'a, T::Base>>)>
223where
224    T: Parse<'a>,
225    Ty: GrammarType,
226{
227    let colon_sep = delimited(ws::<Ty>, tag_char(':'), ws::<Ty>);
228    tuple((
229        map(var_name, Spanned::from),
230        opt(preceded(colon_sep, expr::<T, Ty>)),
231    ))(input)
232}
233
234fn object_expr<'a, T, Ty>(input: InputSpan<'a>) -> NomResult<'a, SpannedExpr<'a, T::Base>>
235where
236    T: Parse<'a>,
237    Ty: GrammarType,
238{
239    let object = preceded(
240        terminated(tag("#{"), ws::<Ty>),
241        cut(terminated(
242            terminated(
243                separated_list0(comma_sep::<Ty>, object_expr_field::<T, Ty>),
244                opt(comma_sep::<Ty>),
245            ),
246            preceded(ws::<Ty>, tag_char('}')),
247        )),
248    );
249
250    map(with_span(object), |spanned| {
251        spanned.map_extra(|fields| Expr::Object(ObjectExpr { fields }))
252    })(input)
253}
254
255/// Parses a function definition and wraps it into an `Expr`.
256fn fn_def_expr<'a, T, Ty>(input: InputSpan<'a>) -> NomResult<'a, SpannedExpr<'a, T::Base>>
257where
258    T: Parse<'a>,
259    Ty: GrammarType,
260{
261    map(with_span(fn_def::<T, Ty>), |spanned| {
262        spanned.map_extra(Expr::FnDefinition)
263    })(input)
264}
265
266/// Parses a simple expression, i.e., one not containing binary operations or function calls.
267///
268/// From the construction, the evaluation priorities within such an expression are always higher
269/// than for possible binary ops surrounding it.
270fn simplest_expr<'a, T, Ty>(input: InputSpan<'a>) -> NomResult<'a, SpannedExpr<'a, T::Base>>
271where
272    T: Parse<'a>,
273    Ty: GrammarType,
274{
275    fn error<'b, T>(input: InputSpan<'b>) -> NomResult<'b, SpannedExpr<'b, T::Base>>
276    where
277        T: Parse<'b>,
278    {
279        let e = ErrorKind::Leftovers.with_span(&input.into());
280        Err(NomErr::Error(e))
281    }
282
283    let block_parser = if T::FEATURES.contains(Features::BLOCKS) {
284        block_expr::<T, Ty>
285    } else {
286        error::<T>
287    };
288
289    let object_parser = if T::FEATURES.contains(Features::OBJECTS) {
290        object_expr::<T, Ty>
291    } else {
292        error::<T>
293    };
294
295    let fn_def_parser = if T::FEATURES.contains(Features::FN_DEFINITIONS) {
296        fn_def_expr::<T, Ty>
297    } else {
298        error::<T>
299    };
300
301    alt((
302        map(with_span(<T::Base>::parse_literal), |span| {
303            span.map_extra(Expr::Literal)
304        }),
305        map(with_span(var_name), |span| {
306            span.copy_with_extra(Expr::Variable)
307        }),
308        fn_def_parser,
309        map(
310            with_span(tuple((
311                terminated(with_span(one_of("-!")), ws::<Ty>),
312                expr_with_calls::<T, Ty>,
313            ))),
314            |spanned| {
315                spanned.map_extra(|(op, inner)| Expr::Unary {
316                    op: UnaryOp::from_span(op),
317                    inner: Box::new(inner),
318                })
319            },
320        ),
321        block_parser,
322        object_parser,
323        paren_expr::<T, Ty>,
324    ))(input)
325}
326
327#[derive(Debug)]
328struct MethodOrFnCall<'a, T: Grammar<'a>> {
329    fn_name: Option<InputSpan<'a>>,
330    args: Option<Vec<SpannedExpr<'a, T>>>,
331}
332
333impl<'a, T: Grammar<'a>> MethodOrFnCall<'a, T> {
334    fn is_method(&self) -> bool {
335        self.fn_name.is_some() && self.args.is_some()
336    }
337}
338
339type MethodParseResult<'a, T> = NomResult<'a, MethodOrFnCall<'a, <T as Parse<'a>>::Base>>;
340
341fn fn_call<'a, T, Ty>(input: InputSpan<'a>) -> MethodParseResult<'a, T>
342where
343    T: Parse<'a>,
344    Ty: GrammarType,
345{
346    map(fn_args::<T, Ty>, |(args, _)| MethodOrFnCall {
347        fn_name: None,
348        args: Some(args),
349    })(input)
350}
351
352fn method_or_fn_call<'a, T, Ty>(input: InputSpan<'a>) -> MethodParseResult<'a, T>
353where
354    T: Parse<'a>,
355    Ty: GrammarType,
356{
357    let var_name_or_digits = alt((var_name, take_while1(|c: char| c.is_ascii_digit())));
358    let method_parser = map_res(
359        tuple((var_name_or_digits, opt(fn_args::<T, Ty>))),
360        |(fn_name, maybe_args)| {
361            if maybe_args.is_some() && !is_valid_variable_name(fn_name.fragment()) {
362                Err(ErrorKind::LiteralName)
363            } else {
364                Ok(MethodOrFnCall {
365                    fn_name: Some(fn_name),
366                    args: maybe_args.map(|(args, _)| args),
367                })
368            }
369        },
370    );
371
372    alt((
373        preceded(tuple((tag_char('.'), ws::<Ty>)), cut(method_parser)),
374        fn_call::<T, Ty>,
375    ))(input)
376}
377
378/// Expression, which includes, besides `simplest_expr`s, function calls.
379fn expr_with_calls<'a, T, Ty>(input: InputSpan<'a>) -> NomResult<'a, SpannedExpr<'a, T::Base>>
380where
381    T: Parse<'a>,
382    Ty: GrammarType,
383{
384    let method_or_fn_call = if T::FEATURES.contains(Features::METHODS) {
385        method_or_fn_call::<T, Ty>
386    } else {
387        fn_call::<T, Ty>
388    };
389
390    let mut parser = tuple((
391        simplest_expr::<T, Ty>,
392        many0(with_span(preceded(ws::<Ty>, method_or_fn_call))),
393    ));
394    parser(input).and_then(|(rest, (base, calls))| {
395        fold_args(input, base, calls).map(|folded| (rest, folded))
396    })
397}
398
399/// Simple expression, which includes `expr_with_calls` and type casts.
400fn simple_expr<'a, T, Ty>(input: InputSpan<'a>) -> NomResult<'a, SpannedExpr<'a, T::Base>>
401where
402    T: Parse<'a>,
403    Ty: GrammarType,
404{
405    let as_keyword = delimited(ws::<Ty>, tag("as"), mandatory_ws::<Ty>);
406    let parser = tuple((
407        expr_with_calls::<T, Ty>,
408        many0(preceded(as_keyword, cut(with_span(<T::Base>::parse_type)))),
409    ));
410
411    map(parser, |(base, casts)| {
412        casts.into_iter().fold(base, |value, ty| {
413            let united_span = unite_spans(input, &value, &ty);
414            united_span.copy_with_extra(Expr::TypeCast {
415                value: Box::new(value),
416                ty,
417            })
418        })
419    })(input)
420}
421
422#[allow(clippy::option_if_let_else, clippy::range_plus_one)]
423// ^-- See explanations in the function code.
424fn fold_args<'a, T: Grammar<'a>>(
425    input: InputSpan<'a>,
426    mut base: SpannedExpr<'a, T>,
427    calls: Vec<Spanned<'a, MethodOrFnCall<'a, T>>>,
428) -> Result<SpannedExpr<'a, T>, NomErr<Error<'a>>> {
429    // Do we need to reorder unary op application and method calls? This is only applicable if:
430    //
431    // - `base` is a literal (as a corollary, `-` / `!` may be a start of a literal)
432    // - The next call is a method
433    // - A literal is parsed without the unary op.
434    //
435    // If all these conditions hold, we reorder the unary op to execute *after* all calls.
436    // This is necessary to correctly parse `-1.abs()` as `-(1.abs())`, not as `(-1).abs()`.
437    let mut maybe_reordered_op = None;
438
439    if matches!(base.extra, Expr::Literal(_)) {
440        match calls.first() {
441            Some(call) if !call.extra.is_method() => {
442                // Bogus function call, such as `1(2, 3)` or `1.x`.
443                let e = ErrorKind::LiteralName.with_span(&base);
444                return Err(NomErr::Failure(e));
445            }
446            // Indexing should be safe: literals must have non-empty span.
447            Some(_) => maybe_reordered_op = UnaryOp::try_from_byte(base.fragment().as_bytes()[0]),
448            None => { /* Special processing is not required. */ }
449        }
450    }
451
452    let reordered_op = if let Some(reordered_op) = maybe_reordered_op {
453        let lit_start = base.location_offset() - input.location_offset();
454        let unsigned_lit_input = input.slice((lit_start + 1)..(lit_start + base.fragment().len()));
455
456        if let Ok((_, unsigned_lit)) = T::parse_literal(unsigned_lit_input) {
457            base = SpannedExpr::new(unsigned_lit_input, Expr::Literal(unsigned_lit));
458
459            // `nom::Slice` is not implemented for inclusive range types, so the Clippy warning
460            // cannot be fixed.
461            let op_span = input.slice(lit_start..(lit_start + 1));
462            Some(Spanned::new(op_span, reordered_op))
463        } else {
464            None
465        }
466    } else {
467        None
468    };
469
470    let folded = calls.into_iter().fold(base, |name, call| {
471        let united_span = unite_spans(input, &name, &call);
472
473        // Clippy lint is triggered here. `name` cannot be moved into both branches,
474        // so it's a false positive.
475        let expr = if let Some(fn_name) = call.extra.fn_name {
476            if let Some(args) = call.extra.args {
477                Expr::Method {
478                    name: fn_name.into(),
479                    receiver: Box::new(name),
480                    args,
481                }
482            } else {
483                Expr::FieldAccess {
484                    name: fn_name.into(),
485                    receiver: Box::new(name),
486                }
487            }
488        } else {
489            Expr::Function {
490                name: Box::new(name),
491                args: call.extra.args.expect("Args must be present for functions"),
492            }
493        };
494        united_span.copy_with_extra(expr)
495    });
496
497    Ok(if let Some(unary_op) = reordered_op {
498        unite_spans(input, &unary_op, &folded).copy_with_extra(Expr::Unary {
499            op: unary_op,
500            inner: Box::new(folded),
501        })
502    } else {
503        folded
504    })
505}
506
507/// Parses an expression with binary operations into a tree with the hierarchy reflecting
508/// the evaluation order of the operations.
509fn binary_expr<'a, T, Ty>(input: InputSpan<'a>) -> NomResult<'a, SpannedExpr<'a, T::Base>>
510where
511    T: Parse<'a>,
512    Ty: GrammarType,
513{
514    // First, we parse the expression into a list with simple expressions interspersed
515    // with binary operations. For example, `1 + 2 * foo(x, y)` is parsed into
516    //
517    //     [ 1, +, 2, *, foo(x, y) ]
518    #[rustfmt::skip]
519    let binary_ops = alt((
520        tag("+"), tag("-"), tag("*"), tag("/"), tag("^"), // simple ops
521        tag("=="), tag("!="), // equality comparisons
522        tag("&&"), tag("||"), // logical ops
523        tag(">="), tag("<="), tag(">"), tag("<"), // order comparisons
524    ));
525    let mut binary_ops = map(binary_ops, BinaryOp::from_span);
526
527    let full_binary_ops = move |input| {
528        let (rest, spanned_op) = binary_ops(input)?;
529        if spanned_op.extra.is_supported(T::FEATURES) {
530            Ok((rest, spanned_op))
531        } else {
532            // Immediately drop parsing on an unsupported op, since there are no alternatives.
533            let err = ErrorKind::UnsupportedOp(spanned_op.extra.into());
534            let spanned_err = err.with_span(&spanned_op);
535            Err(NomErr::Failure(spanned_err))
536        }
537    };
538
539    let mut binary_parser = tuple((
540        simple_expr::<T, Ty>,
541        many0(tuple((
542            delimited(ws::<Ty>, full_binary_ops, ws::<Ty>),
543            cut(simple_expr::<T, Ty>),
544        ))),
545    ));
546
547    let (remaining_input, (first, rest)) = binary_parser(input)?;
548    let folded = fold_binary_expr(input, first, rest).map_err(NomErr::Failure)?;
549    Ok((remaining_input, folded))
550}
551
552// After obtaining the list, we fold it paying attention to the operation priorities.
553// We track the `right_contour` of the parsed tree and insert a new operation so that
554// operations in the contour with lower priority remain in place.
555//
556// As an example, consider expression `1 + 2 * foo(x, y) - 7` split into list
557// `[ 1, +, 2, *, foo(x, y), -, 7 ]`. First, we form a tree
558//
559//   +
560//  / \
561// 1   2
562//
563// Then, we find the place to insert the `*` op and `foo(x, y)` operand. Since `*` has
564// a higher priority than `+`, we insert it *below* the `+` op:
565//
566//   +
567//  / \
568// 1   *
569//    / \
570//   2  foo(x, y)
571//
572// The next op `-` has the same priority as the first op in the right contour (`+`), so
573// we insert it *above* it:
574//
575//    -
576//   / \
577//   +  7
578//  / \
579// 1   *
580//    / \
581//   2  foo(x, y)
582fn fold_binary_expr<'a, T: Grammar<'a>>(
583    input: InputSpan<'a>,
584    first: SpannedExpr<'a, T>,
585    rest: Vec<(Spanned<'a, BinaryOp>, SpannedExpr<'a, T>)>,
586) -> Result<SpannedExpr<'a, T>, Error<'a>> {
587    let mut right_contour: Vec<BinaryOp> = vec![];
588
589    rest.into_iter().try_fold(first, |mut acc, (new_op, expr)| {
590        let united_span = unite_spans(input, &acc, &expr);
591
592        let insert_pos = right_contour
593            .iter()
594            .position(|past_op| past_op.priority() >= new_op.extra.priority())
595            .unwrap_or_else(|| right_contour.len());
596
597        // We determine the error span later.
598        let chained_comparison = right_contour.get(insert_pos).map_or(false, |past_op| {
599            past_op.is_comparison() && new_op.extra.is_comparison()
600        });
601
602        right_contour.truncate(insert_pos);
603        right_contour.push(new_op.extra);
604
605        if insert_pos == 0 {
606            if chained_comparison {
607                return Err(ErrorKind::ChainedComparison.with_span(&united_span));
608            }
609
610            Ok(united_span.copy_with_extra(Expr::Binary {
611                lhs: Box::new(acc),
612                op: new_op,
613                rhs: Box::new(expr),
614            }))
615        } else {
616            let mut parent = &mut acc;
617            for _ in 1..insert_pos {
618                parent = match parent.extra {
619                    Expr::Binary { ref mut rhs, .. } => rhs,
620                    _ => unreachable!(),
621                };
622            }
623
624            *parent = unite_spans(input, &parent, &expr).copy_with_extra(parent.extra.clone());
625            if let Expr::Binary { ref mut rhs, .. } = parent.extra {
626                let rhs_span = unite_spans(input, rhs, &expr);
627                if chained_comparison {
628                    return Err(ErrorKind::ChainedComparison.with_span(&rhs_span));
629                }
630
631                let dummy = Box::new(rhs.copy_with_extra(Expr::Variable));
632                let old_rhs = mem::replace(rhs, dummy);
633                let new_expr = Expr::Binary {
634                    lhs: old_rhs,
635                    op: new_op,
636                    rhs: Box::new(expr),
637                };
638                *rhs = Box::new(rhs_span.copy_with_extra(new_expr));
639            }
640            acc = united_span.copy_with_extra(acc.extra);
641            Ok(acc)
642        }
643    })
644}
645
646fn expr<'a, T, Ty>(input: InputSpan<'a>) -> NomResult<'a, SpannedExpr<'a, T::Base>>
647where
648    T: Parse<'a>,
649    Ty: GrammarType,
650{
651    context(Context::Expr.to_str(), binary_expr::<T, Ty>)(input)
652}
653
654fn comma_sep<Ty: GrammarType>(input: InputSpan<'_>) -> NomResult<'_, char> {
655    delimited(ws::<Ty>, tag_char(','), ws::<Ty>)(input)
656}
657
658fn comma_separated_lvalues<'a, T, Ty>(
659    input: InputSpan<'a>,
660) -> NomResult<'a, Vec<GrammarLvalue<'a, T>>>
661where
662    T: Parse<'a>,
663    Ty: GrammarType,
664{
665    separated_list0(comma_sep::<Ty>, lvalue::<T, Ty>)(input)
666}
667
668fn destructure_rest<'a, T, Ty>(
669    input: InputSpan<'a>,
670) -> NomResult<'a, Spanned<'a, DestructureRest<'a, <T::Base as Grammar<'a>>::Type>>>
671where
672    T: Parse<'a>,
673    Ty: GrammarType,
674{
675    map(
676        with_span(preceded(
677            terminated(tag("..."), ws::<Ty>),
678            cut(opt(simple_lvalue_with_type::<T, Ty>)),
679        )),
680        |spanned| {
681            spanned.map_extra(|maybe_lvalue| {
682                maybe_lvalue.map_or(DestructureRest::Unnamed, |lvalue| DestructureRest::Named {
683                    variable: lvalue.with_no_extra(),
684                    ty: match lvalue.extra {
685                        Lvalue::Variable { ty } => ty,
686                        _ => None,
687                    },
688                })
689            })
690        },
691    )(input)
692}
693
694type DestructureTail<'a, T> = (
695    Spanned<'a, DestructureRest<'a, T>>,
696    Option<Vec<SpannedLvalue<'a, T>>>,
697);
698
699fn destructure_tail<'a, T, Ty>(
700    input: InputSpan<'a>,
701) -> NomResult<'a, DestructureTail<'a, <T::Base as Grammar<'a>>::Type>>
702where
703    T: Parse<'a>,
704    Ty: GrammarType,
705{
706    tuple((
707        destructure_rest::<T, Ty>,
708        opt(preceded(comma_sep::<Ty>, comma_separated_lvalues::<T, Ty>)),
709    ))(input)
710}
711
712/// Parse the destructuring *without* the surrounding delimiters.
713fn destructure<'a, T, Ty>(
714    input: InputSpan<'a>,
715) -> NomResult<'a, Destructure<'a, <T::Base as Grammar<'a>>::Type>>
716where
717    T: Parse<'a>,
718    Ty: GrammarType,
719{
720    let main_parser = alt((
721        // `destructure_tail` has fast fail path: the input must start with `...`.
722        map(destructure_tail::<T, Ty>, |rest| (vec![], Some(rest))),
723        tuple((
724            comma_separated_lvalues::<T, Ty>,
725            opt(preceded(comma_sep::<Ty>, destructure_tail::<T, Ty>)),
726        )),
727    ));
728    // Allow for `,`-terminated lists.
729    let main_parser = terminated(main_parser, opt(comma_sep::<Ty>));
730
731    map(main_parser, |(start, maybe_rest)| {
732        if let Some((middle, end)) = maybe_rest {
733            Destructure {
734                start,
735                middle: Some(middle),
736                end: end.unwrap_or_default(),
737            }
738        } else {
739            Destructure {
740                start,
741                middle: None,
742                end: vec![],
743            }
744        }
745    })(input)
746}
747
748type GrammarLvalue<'a, T> = SpannedLvalue<'a, <<T as Parse<'a>>::Base as Grammar<'a>>::Type>;
749
750fn parenthesized_destructure<'a, T, Ty>(input: InputSpan<'a>) -> NomResult<'a, GrammarLvalue<'a, T>>
751where
752    T: Parse<'a>,
753    Ty: GrammarType,
754{
755    with_span(map(
756        delimited(
757            terminated(tag_char('('), ws::<Ty>),
758            destructure::<T, Ty>,
759            preceded(ws::<Ty>, tag_char(')')),
760        ),
761        Lvalue::Tuple,
762    ))(input)
763}
764
765/// Simple lvalue with an optional type annotation, e.g., `x` or `x: Num`.
766fn simple_lvalue_with_type<'a, T, Ty>(input: InputSpan<'a>) -> NomResult<'a, GrammarLvalue<'a, T>>
767where
768    T: Parse<'a>,
769    Ty: GrammarType,
770{
771    map(
772        tuple((
773            var_name,
774            opt(preceded(
775                delimited(ws::<Ty>, tag_char(':'), ws::<Ty>),
776                cut(with_span(<T::Base>::parse_type)),
777            )),
778        )),
779        |(name, ty)| Spanned::new(name, Lvalue::Variable { ty }),
780    )(input)
781}
782
783fn simple_lvalue_without_type<'a, T>(input: InputSpan<'a>) -> NomResult<'a, GrammarLvalue<'a, T>>
784where
785    T: Parse<'a>,
786{
787    map(var_name, |name| {
788        Spanned::new(name, Lvalue::Variable { ty: None })
789    })(input)
790}
791
792fn object_destructure_field<'a, T, Ty>(
793    input: InputSpan<'a>,
794) -> NomResult<'a, ObjectDestructureField<'a, <T::Base as Grammar<'a>>::Type>>
795where
796    T: Parse<'a>,
797    Ty: GrammarType,
798{
799    let field_sep = alt((tag(":"), tag("->")));
800    let field_sep = tuple((ws::<Ty>, field_sep, ws::<Ty>));
801    let field = tuple((var_name, opt(preceded(field_sep, lvalue::<T, Ty>))));
802    map(field, |(name, maybe_binding)| ObjectDestructureField {
803        field_name: Spanned::new(name, ()),
804        binding: maybe_binding,
805    })(input)
806}
807
808fn object_destructure<'a, T, Ty>(
809    input: InputSpan<'a>,
810) -> NomResult<'a, ObjectDestructure<'a, <T::Base as Grammar<'a>>::Type>>
811where
812    T: Parse<'a>,
813    Ty: GrammarType,
814{
815    let inner = separated_list1(comma_sep::<Ty>, object_destructure_field::<T, Ty>);
816    let inner = terminated(inner, opt(comma_sep::<Ty>));
817    let inner = delimited(
818        terminated(tag_char('{'), ws::<Ty>),
819        inner,
820        preceded(ws::<Ty>, tag_char('}')),
821    );
822    map(inner, |fields| ObjectDestructure { fields })(input)
823}
824
825fn mapped_object_destructure<'a, T, Ty>(input: InputSpan<'a>) -> NomResult<'a, GrammarLvalue<'a, T>>
826where
827    T: Parse<'a>,
828    Ty: GrammarType,
829{
830    with_span(map(object_destructure::<T, Ty>, Lvalue::Object))(input)
831}
832
833/// Parses an `Lvalue`.
834fn lvalue<'a, T, Ty>(input: InputSpan<'a>) -> NomResult<'a, GrammarLvalue<'a, T>>
835where
836    T: Parse<'a>,
837    Ty: GrammarType,
838{
839    fn error<'b, T>(input: InputSpan<'b>) -> NomResult<'b, GrammarLvalue<'b, T>>
840    where
841        T: Parse<'b>,
842    {
843        let e = ErrorKind::Leftovers.with_span(&input.into());
844        Err(NomErr::Error(e))
845    }
846
847    let simple_lvalue = if T::FEATURES.contains(Features::TYPE_ANNOTATIONS) {
848        simple_lvalue_with_type::<T, Ty>
849    } else {
850        simple_lvalue_without_type::<T>
851    };
852
853    let destructure = if T::FEATURES.contains(Features::TUPLES) {
854        parenthesized_destructure::<T, Ty>
855    } else {
856        error::<T>
857    };
858
859    let object_destructure = if T::FEATURES.contains(Features::OBJECTS) {
860        mapped_object_destructure::<T, Ty>
861    } else {
862        error::<T>
863    };
864
865    alt((destructure, object_destructure, simple_lvalue))(input)
866}
867
868#[allow(clippy::option_if_let_else)]
869fn statement<'a, T, Ty>(input: InputSpan<'a>) -> NomResult<'a, SpannedStatement<'a, T::Base>>
870where
871    T: Parse<'a>,
872    Ty: GrammarType,
873{
874    let assignment = tuple((tag("="), peek(not(tag_char('=')))));
875    let assignment_parser = tuple((
876        opt(terminated(
877            lvalue::<T, Ty>,
878            delimited(ws::<Ty>, assignment, ws::<Ty>),
879        )),
880        expr::<T, Ty>,
881    ));
882
883    with_span(map(assignment_parser, |(lvalue, rvalue)| {
884        // Clippy lint is triggered here. `rvalue` cannot be moved into both branches, so it's a false positive.
885        if let Some(lvalue) = lvalue {
886            Statement::Assignment {
887                lhs: lvalue,
888                rhs: Box::new(rvalue),
889            }
890        } else {
891            Statement::Expr(rvalue)
892        }
893    }))(input)
894}
895
896/// Parses a complete list of statements.
897pub(crate) fn statements<'a, T>(input_span: InputSpan<'a>) -> Result<Block<'a, T::Base>, Error<'a>>
898where
899    T: Parse<'a>,
900{
901    if !input_span.fragment().is_ascii() {
902        return Err(Error::new(input_span, ErrorKind::NonAsciiInput));
903    }
904    statements_inner::<T, Complete>(input_span)
905}
906
907/// Parses a potentially incomplete list of statements.
908pub(crate) fn streaming_statements<'a, T>(
909    input_span: InputSpan<'a>,
910) -> Result<Block<'a, T::Base>, Error<'a>>
911where
912    T: Parse<'a>,
913{
914    if !input_span.fragment().is_ascii() {
915        return Err(Error::new(input_span, ErrorKind::NonAsciiInput));
916    }
917
918    statements_inner::<T, Complete>(input_span)
919        .or_else(|_| statements_inner::<T, Streaming>(input_span))
920}
921
922fn statements_inner<'a, T, Ty>(input_span: InputSpan<'a>) -> Result<Block<'a, T::Base>, Error<'a>>
923where
924    T: Parse<'a>,
925    Ty: GrammarType,
926{
927    delimited(ws::<Ty>, separated_statements::<T, Ty>, ws::<Ty>)(input_span)
928        .map_err(|e| match e {
929            NomErr::Failure(e) | NomErr::Error(e) => e,
930            NomErr::Incomplete(_) => ErrorKind::Incomplete.with_span(&input_span.into()),
931        })
932        .and_then(|(remaining, statements)| {
933            if remaining.fragment().is_empty() {
934                Ok(statements)
935            } else {
936                Err(ErrorKind::Leftovers.with_span(&remaining.into()))
937            }
938        })
939}
940
941fn separated_statement<'a, T, Ty>(
942    input: InputSpan<'a>,
943) -> NomResult<'a, SpannedStatement<'a, T::Base>>
944where
945    T: Parse<'a>,
946    Ty: GrammarType,
947{
948    terminated(statement::<T, Ty>, preceded(ws::<Ty>, tag_char(';')))(input)
949}
950
951/// List of statements separated by semicolons.
952fn separated_statements<'a, T, Ty>(input: InputSpan<'a>) -> NomResult<'a, Block<'a, T::Base>>
953where
954    T: Parse<'a>,
955    Ty: GrammarType,
956{
957    map(
958        tuple((
959            many0(terminated(separated_statement::<T, Ty>, ws::<Ty>)),
960            opt(expr::<T, Ty>),
961        )),
962        |(statements, return_value)| Block {
963            statements,
964            return_value: return_value.map(Box::new),
965        },
966    )(input)
967}
968
969/// Block of statements, e.g., `{ x = 3; x + y }`.
970fn block<'a, T, Ty>(input: InputSpan<'a>) -> NomResult<'a, Block<'a, T::Base>>
971where
972    T: Parse<'a>,
973    Ty: GrammarType,
974{
975    preceded(
976        terminated(tag_char('{'), ws::<Ty>),
977        cut(terminated(
978            separated_statements::<T, Ty>,
979            preceded(ws::<Ty>, tag_char('}')),
980        )),
981    )(input)
982}
983
984/// Function definition, e.g., `|x, y: Sc| { x + y }`.
985fn fn_def<'a, T, Ty>(input: InputSpan<'a>) -> NomResult<'a, FnDefinition<'a, T::Base>>
986where
987    T: Parse<'a>,
988    Ty: GrammarType,
989{
990    let body_parser = alt((
991        block::<T, Ty>,
992        map(expr::<T, Ty>, |spanned| Block {
993            statements: vec![],
994            return_value: Some(Box::new(spanned)),
995        }),
996    ));
997
998    let args_parser = preceded(
999        terminated(tag_char('|'), ws::<Ty>),
1000        cut(terminated(
1001            destructure::<T, Ty>,
1002            preceded(ws::<Ty>, tag_char('|')),
1003        )),
1004    );
1005
1006    let parser = tuple((with_span(args_parser), cut(preceded(ws::<Ty>, body_parser))));
1007    map(parser, |(args, body)| FnDefinition { args, body })(input)
1008}