aiken_lang/parser/expr/
record_update.rs

1use chumsky::prelude::*;
2
3use crate::{
4    ast,
5    expr::UntypedExpr,
6    parser::{error::ParseError, token::Token},
7};
8
9pub fn parser(
10    r: Recursive<'_, Token, UntypedExpr, ParseError>,
11) -> impl Parser<Token, UntypedExpr, Error = ParseError> + '_ {
12    select! {Token::Name { name } => name}
13        .map_with_span(|module, span: ast::Span| (module, span))
14        .then_ignore(just(Token::Dot))
15        .or_not()
16        .then(select! {Token::UpName { name } => name}.map_with_span(|name, span| (name, span)))
17        .then(
18            just(Token::DotDot)
19                .ignore_then(r.clone())
20                .then(
21                    just(Token::Comma)
22                        .ignore_then(
23                            choice((
24                                select! { Token::Name {name} => name }
25                                    .then_ignore(just(Token::Colon))
26                                    .then(r.clone())
27                                    .map_with_span(|(label, value), span| {
28                                        ast::UntypedRecordUpdateArg {
29                                            label,
30                                            value,
31                                            location: span,
32                                        }
33                                    }),
34                                select! {Token::Name {name} => name}.map_with_span(|name, span| {
35                                    ast::UntypedRecordUpdateArg {
36                                        location: span,
37                                        value: UntypedExpr::Var {
38                                            name: name.clone(),
39                                            location: span,
40                                        },
41                                        label: name,
42                                    }
43                                }),
44                            ))
45                            .separated_by(just(Token::Comma))
46                            .allow_trailing(),
47                        )
48                        .or_not(),
49                )
50                .delimited_by(just(Token::LeftBrace), just(Token::RightBrace))
51                .map_with_span(|a, span: ast::Span| (a, span)),
52        )
53        .map(|((module, (name, n_span)), ((spread, opt_args), span))| {
54            let constructor = if let Some((module, m_span)) = module {
55                UntypedExpr::FieldAccess {
56                    location: m_span.union(n_span),
57                    label: name,
58                    container: Box::new(UntypedExpr::Var {
59                        location: m_span,
60                        name: module,
61                    }),
62                }
63            } else {
64                UntypedExpr::Var {
65                    location: n_span,
66                    name,
67                }
68            };
69
70            let spread_span = spread.location();
71
72            let location = ast::Span::new((), spread_span.start - 2..spread_span.end);
73
74            let spread = ast::RecordUpdateSpread {
75                base: Box::new(spread),
76                location,
77            };
78
79            UntypedExpr::RecordUpdate {
80                location: constructor.location().union(span),
81                constructor: Box::new(constructor),
82                spread,
83                arguments: opt_args.unwrap_or_default(),
84            }
85        })
86}
87
88#[cfg(test)]
89mod tests {
90    use crate::assert_expr;
91
92    #[test]
93    fn record_update_basic() {
94        assert_expr!(r#"User { ..user, name: "Aiken", age }"#);
95    }
96}