aiken_lang/parser/expr/
mod.rs

1use chumsky::prelude::*;
2use vec1::Vec1;
3
4mod and_or_chain;
5mod anonymous_binop;
6pub mod anonymous_function;
7pub mod assignment;
8mod block;
9pub(crate) mod bytearray;
10mod chained;
11mod fail_todo_trace;
12mod if_else;
13mod int;
14mod list;
15mod pair;
16mod record;
17mod record_update;
18mod sequence;
19pub mod string;
20mod tuple;
21mod var;
22pub mod when;
23
24use super::{error::ParseError, token::Token};
25use crate::{ast, expr::UntypedExpr};
26pub use and_or_chain::parser as and_or_chain;
27pub use anonymous_function::parser as anonymous_function;
28pub use block::parser as block;
29pub use bytearray::parser as bytearray;
30pub use chained::parser as chained;
31pub use fail_todo_trace::parser as fail_todo_trace;
32pub use if_else::parser as if_else;
33pub use int::parser as int;
34pub use list::parser as list;
35pub use pair::parser as pair;
36pub use record::parser as record;
37pub use record_update::parser as record_update;
38pub use sequence::parser as sequence;
39pub use string::parser as string;
40pub use tuple::parser as tuple;
41pub use var::parser as var;
42pub use when::parser as when;
43
44pub fn parser(
45    sequence: Recursive<'_, Token, UntypedExpr, ParseError>,
46) -> impl Parser<Token, UntypedExpr, Error = ParseError> + '_ {
47    recursive(|expression| {
48        choice((
49            fail_todo_trace(expression.clone(), sequence.clone()),
50            pure_expression(sequence, expression),
51        ))
52    })
53}
54
55pub fn pure_expression<'a>(
56    sequence: Recursive<'a, Token, UntypedExpr, ParseError>,
57    expression: Recursive<'a, Token, UntypedExpr, ParseError>,
58) -> impl Parser<Token, UntypedExpr, Error = ParseError> + 'a {
59    // Negate
60    let op = choice((
61        just(Token::Bang).to(ast::UnOp::Not),
62        choice((just(Token::Minus), just(Token::NewLineMinus)))
63            // NOTE: Prevent conflict with usage for '-' as a standalone binary op.
64            // This will make '-' parse when used as standalone binop in a function call.
65            // For example:
66            //
67            //    foo(a, -, b)
68            //
69            // but it'll fail in a let-binding:
70            //
71            //    let foo = -
72            //
73            // which seems acceptable.
74            .then_ignore(just(Token::Comma).not().rewind())
75            .to(ast::UnOp::Negate),
76    ));
77
78    let unary = op
79        .map_with_span(|op, span| (op, span))
80        .repeated()
81        .then(chained(sequence, expression))
82        .foldr(|(un_op, span), value| UntypedExpr::UnOp {
83            op: un_op,
84            location: span.union(value.location()),
85            value: Box::new(value),
86        })
87        .boxed();
88
89    // Product
90    let op = choice((
91        just(Token::Star).to(ast::BinOp::MultInt),
92        just(Token::Slash).to(ast::BinOp::DivInt),
93        just(Token::Percent).to(ast::BinOp::ModInt),
94    ));
95
96    let product = unary
97        .clone()
98        .then(op.then(unary).repeated())
99        .foldl(|a, (op, b)| UntypedExpr::BinOp {
100            location: a.location().union(b.location()),
101            name: op,
102            left: Box::new(a),
103            right: Box::new(b),
104        })
105        .boxed();
106
107    // Sum
108    let op = choice((
109        just(Token::Plus).to(ast::BinOp::AddInt),
110        just(Token::Minus).to(ast::BinOp::SubInt),
111    ));
112
113    let sum = product
114        .clone()
115        .then(op.then(product).repeated())
116        .foldl(|a, (op, b)| UntypedExpr::BinOp {
117            location: a.location().union(b.location()),
118            name: op,
119            left: Box::new(a),
120            right: Box::new(b),
121        })
122        .boxed();
123
124    // Comparison
125    let op = choice((
126        just(Token::EqualEqual).to(ast::BinOp::Eq),
127        just(Token::NotEqual).to(ast::BinOp::NotEq),
128        just(Token::Less).to(ast::BinOp::LtInt),
129        just(Token::Greater).to(ast::BinOp::GtInt),
130        just(Token::LessEqual).to(ast::BinOp::LtEqInt),
131        just(Token::GreaterEqual).to(ast::BinOp::GtEqInt),
132    ));
133
134    let comparison = sum
135        .clone()
136        .then(op.then(sum).repeated())
137        .foldl(|a, (op, b)| UntypedExpr::BinOp {
138            location: a.location().union(b.location()),
139            name: op,
140            left: Box::new(a),
141            right: Box::new(b),
142        })
143        .boxed();
144
145    // Conjunction
146    //
147    // NOTE: This can be written in a nicer way with `foldl_with` in chumsky =^ 1.0.0.
148    // DO NOT however try to write this as:
149    //
150    //  comparison
151    //    .clone()
152    //    .then(op)
153    //    .repeated
154    //    .then(comparison)
155    //    .foldr(...)
156    //
157    // This has somehow incredibly slow performances in Chumsky. Hence the approach below.
158    let op = just(Token::AmperAmper).to(ast::BinOp::And);
159    let conjunction = comparison
160        .clone()
161        .map(|e| vec![e])
162        .clone()
163        .then(op.then(comparison).repeated())
164        .foldl(|a, (_op, b)| {
165            let mut tail = vec![b];
166            tail.extend(a);
167            tail
168        })
169        .map(|xs| {
170            xs.into_iter()
171                .reduce(|right, left| UntypedExpr::BinOp {
172                    location: left.location().union(right.location()),
173                    name: ast::BinOp::And,
174                    left: Box::new(left),
175                    right: Box::new(right),
176                })
177                .unwrap()
178        })
179        .boxed();
180
181    // NOTE: see comment about conjunctions just above.
182    // Disjunction
183    let op = just(Token::VbarVbar).to(ast::BinOp::Or);
184    let disjunction = conjunction
185        .clone()
186        .map(|e| vec![e])
187        .then(op.then(conjunction).repeated())
188        .foldl(|a, (_op, b)| {
189            let mut tail = vec![b];
190            tail.extend(a);
191            tail
192        })
193        .map(|xs| {
194            xs.into_iter()
195                .reduce(|right, left| UntypedExpr::BinOp {
196                    location: left.location().union(right.location()),
197                    name: ast::BinOp::Or,
198                    left: Box::new(left),
199                    right: Box::new(right),
200                })
201                .unwrap()
202        })
203        .boxed();
204
205    // Pipeline
206    disjunction
207        .clone()
208        .then(
209            choice((just(Token::Pipe), just(Token::NewLinePipe)))
210                .then(disjunction)
211                .repeated(),
212        )
213        .foldl(|l, (pipe, r)| {
214            if let UntypedExpr::PipeLine {
215                mut expressions,
216                one_liner,
217            } = l
218            {
219                expressions.push(r);
220                return UntypedExpr::PipeLine {
221                    expressions,
222                    one_liner,
223                };
224            }
225
226            let mut expressions = Vec1::new(l);
227            expressions.push(r);
228            UntypedExpr::PipeLine {
229                expressions,
230                one_liner: pipe != Token::NewLinePipe,
231            }
232        })
233}
234
235#[cfg(test)]
236mod tests {
237    use crate::assert_expr;
238
239    #[test]
240    fn plus_binop() {
241        assert_expr!("a + 1");
242    }
243
244    #[test]
245    fn pipeline() {
246        assert_expr!(
247            r#"
248            a + 2
249            |> add_one
250            |> add_one
251            "#
252        );
253    }
254
255    #[test]
256    fn field_access() {
257        assert_expr!("user.name");
258    }
259
260    #[test]
261    fn function_invoke() {
262        assert_expr!(
263            r#"
264            let x = add_one(3)
265
266            let map_add_x = list.map(_, fn (y) { x + y })
267
268            map_add_x([ 1, 2, 3 ])
269            "#
270        );
271    }
272}