1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
use chumsky::prelude::*;
use crate::{
ast,
expr::UntypedExpr,
parser::{
error::{self, ParseError},
token::Token,
},
};
pub fn parser(
r: Recursive<'_, Token, UntypedExpr, ParseError>,
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + '_ {
choice((
select! {Token::Name { name } => name}
.map_with_span(|module, span: ast::Span| (module, span))
.then_ignore(just(Token::Dot))
.or_not()
.then(select! {Token::UpName { name } => name}.map_with_span(|name, span| (name, span)))
.then(
choice((
select! {Token::Name {name} => name}
.then_ignore(just(Token::Colon))
.then(choice((
r.clone(),
select! {Token::DiscardName {name} => name }.validate(
|_name, span, emit| {
emit(ParseError::expected_input_found(
span,
None,
Some(error::Pattern::Discard),
));
UntypedExpr::Var {
location: span,
name: ast::CAPTURE_VARIABLE.to_string(),
}
},
),
)))
.map_with_span(|(label, value), span| ast::CallArg {
location: span,
value,
label: Some(label),
}),
choice((
select! {Token::Name {name} => name}.map_with_span(|name, span| {
(
UntypedExpr::Var {
name: name.clone(),
location: span,
},
name,
)
}),
select! {Token::DiscardName {name} => name }.validate(
|name, span, emit| {
emit(ParseError::expected_input_found(
span,
None,
Some(error::Pattern::Discard),
));
(
UntypedExpr::Var {
location: span,
name: ast::CAPTURE_VARIABLE.to_string(),
},
name,
)
},
),
))
.map(|(value, name)| ast::CallArg {
location: value.location(),
value,
label: Some(name),
}),
))
.separated_by(just(Token::Comma))
.allow_trailing()
.delimited_by(just(Token::LeftBrace), just(Token::RightBrace)),
),
select! {Token::Name { name } => name}
.map_with_span(|module, span| (module, span))
.then_ignore(just(Token::Dot))
.or_not()
.then(select! {Token::UpName { name } => name}.map_with_span(|name, span| (name, span)))
.then(
select! {Token::Name {name} => name}
.ignored()
.then_ignore(just(Token::Colon))
.validate(|_label, span, emit| {
emit(ParseError::expected_input_found(
span,
None,
Some(error::Pattern::Label),
))
})
.or_not()
.then(choice((
r.clone(),
select! {Token::DiscardName {name} => name }.validate(
|_name, span, emit| {
emit(ParseError::expected_input_found(
span,
None,
Some(error::Pattern::Discard),
));
UntypedExpr::Var {
location: span,
name: ast::CAPTURE_VARIABLE.to_string(),
}
},
),
)))
.map(|(_label, value)| ast::CallArg {
location: value.location(),
value,
label: None,
})
.separated_by(just(Token::Comma))
.allow_trailing()
.delimited_by(just(Token::LeftParen), just(Token::RightParen)),
),
))
.map_with_span(|((module, (name, n_span)), arguments), span| {
let fun = if let Some((module, m_span)) = module {
UntypedExpr::FieldAccess {
location: m_span.union(n_span),
label: name,
container: Box::new(UntypedExpr::Var {
location: m_span,
name: module,
}),
}
} else {
UntypedExpr::Var {
location: n_span,
name,
}
};
UntypedExpr::Call {
arguments,
fun: Box::new(fun),
location: span,
}
})
}
#[cfg(test)]
mod tests {
use crate::assert_expr;
#[test]
fn record_create_labeled() {
assert_expr!(r#"User { name: "Aiken", age, thing: 2 }"#);
}
#[test]
fn record_create_labeled_with_field_access() {
assert_expr!(r#"some_module.User { name: "Aiken", age, thing: 2 }"#);
}
#[test]
fn record_create_unlabeled() {
assert_expr!(r#"some_module.Thing(1, a)"#);
}
}