arithmetic_parser/parser/
mod.rs

1//! Parsers implemented with the help of `nom`.
2
3use nom::{
4    branch::alt,
5    bytes::complete::tag,
6    character::complete::char as tag_char,
7    combinator::{cut, map, not, opt, peek},
8    multi::many0,
9    sequence::{delimited, preceded, terminated},
10    Err as NomErr, Parser as _,
11};
12
13pub use self::helpers::is_valid_variable_name;
14use self::{
15    expr::expr,
16    helpers::{ws, Complete, GrammarType, Streaming},
17    lvalue::{destructure, lvalue},
18};
19use crate::{
20    alloc::{vec, Box},
21    grammars::Parse,
22    spans::with_span,
23    Block, Error, ErrorKind, FnDefinition, InputSpan, NomResult, SpannedStatement, Statement,
24};
25
26mod expr;
27mod helpers;
28mod lvalue;
29#[cfg(test)]
30mod tests;
31
32#[allow(clippy::option_if_let_else)]
33fn statement<T, Ty>(input: InputSpan<'_>) -> NomResult<'_, SpannedStatement<'_, T::Base>>
34where
35    T: Parse,
36    Ty: GrammarType,
37{
38    let assignment = (tag("="), peek(not(tag_char('='))));
39    let assignment_parser = (
40        opt(terminated(
41            lvalue::<T, Ty>,
42            delimited(ws::<Ty>, assignment, ws::<Ty>),
43        )),
44        expr::<T, Ty>,
45    );
46
47    with_span(map(assignment_parser, |(lvalue, rvalue)| {
48        // Clippy lint is triggered here. `rvalue` cannot be moved into both branches, so it's a false positive.
49        if let Some(lvalue) = lvalue {
50            Statement::Assignment {
51                lhs: lvalue,
52                rhs: Box::new(rvalue),
53            }
54        } else {
55            Statement::Expr(rvalue)
56        }
57    }))
58    .parse(input)
59}
60
61/// Parses a complete list of statements.
62pub(crate) fn statements<T>(input_span: InputSpan<'_>) -> Result<Block<'_, T::Base>, Error>
63where
64    T: Parse,
65{
66    if !input_span.fragment().is_ascii() {
67        return Err(Error::new(input_span, ErrorKind::NonAsciiInput));
68    }
69    statements_inner::<T, Complete>(input_span)
70}
71
72/// Parses a potentially incomplete list of statements.
73pub(crate) fn streaming_statements<T>(
74    input_span: InputSpan<'_>,
75) -> Result<Block<'_, T::Base>, Error>
76where
77    T: Parse,
78{
79    if !input_span.fragment().is_ascii() {
80        return Err(Error::new(input_span, ErrorKind::NonAsciiInput));
81    }
82
83    statements_inner::<T, Complete>(input_span)
84        .or_else(|_| statements_inner::<T, Streaming>(input_span))
85}
86
87fn statements_inner<T, Ty>(input_span: InputSpan<'_>) -> Result<Block<'_, T::Base>, Error>
88where
89    T: Parse,
90    Ty: GrammarType,
91{
92    delimited(ws::<Ty>, separated_statements::<T, Ty>, ws::<Ty>)
93        .parse(input_span)
94        .map_err(|e| match e {
95            NomErr::Failure(e) | NomErr::Error(e) => e,
96            NomErr::Incomplete(_) => ErrorKind::Incomplete.with_span(&input_span.into()),
97        })
98        .and_then(|(remaining, statements)| {
99            if remaining.fragment().is_empty() {
100                Ok(statements)
101            } else {
102                Err(ErrorKind::Leftovers.with_span(&remaining.into()))
103            }
104        })
105}
106
107fn separated_statement<T, Ty>(input: InputSpan<'_>) -> NomResult<'_, SpannedStatement<'_, T::Base>>
108where
109    T: Parse,
110    Ty: GrammarType,
111{
112    terminated(statement::<T, Ty>, preceded(ws::<Ty>, tag_char(';'))).parse(input)
113}
114
115/// List of statements separated by semicolons.
116fn separated_statements<T, Ty>(input: InputSpan<'_>) -> NomResult<'_, Block<'_, T::Base>>
117where
118    T: Parse,
119    Ty: GrammarType,
120{
121    map(
122        (
123            many0(terminated(separated_statement::<T, Ty>, ws::<Ty>)),
124            opt(expr::<T, Ty>),
125        ),
126        |(statements, return_value)| Block {
127            statements,
128            return_value: return_value.map(Box::new),
129        },
130    )
131    .parse(input)
132}
133
134/// Block of statements, e.g., `{ x = 3; x + y }`.
135fn block<T, Ty>(input: InputSpan<'_>) -> NomResult<'_, Block<'_, T::Base>>
136where
137    T: Parse,
138    Ty: GrammarType,
139{
140    preceded(
141        terminated(tag_char('{'), ws::<Ty>),
142        cut(terminated(
143            separated_statements::<T, Ty>,
144            preceded(ws::<Ty>, tag_char('}')),
145        )),
146    )
147    .parse(input)
148}
149
150/// Function definition, e.g., `|x, y: Sc| { x + y }`.
151fn fn_def<T, Ty>(input: InputSpan<'_>) -> NomResult<'_, FnDefinition<'_, T::Base>>
152where
153    T: Parse,
154    Ty: GrammarType,
155{
156    let body_parser = alt((
157        block::<T, Ty>,
158        map(expr::<T, Ty>, |spanned| Block {
159            statements: vec![],
160            return_value: Some(Box::new(spanned)),
161        }),
162    ));
163
164    let args_parser = preceded(
165        terminated(tag_char('|'), ws::<Ty>),
166        cut(terminated(
167            destructure::<T, Ty>,
168            preceded(ws::<Ty>, tag_char('|')),
169        )),
170    );
171
172    let parser = (with_span(args_parser), cut(preceded(ws::<Ty>, body_parser)));
173    map(parser, |(args, body)| FnDefinition { args, body }).parse(input)
174}