1 | use nom::branch::alt; |
2 | use nom::bytes::complete::tag; |
3 | use nom::character::complete::{char, one_of}; |
4 | use nom::combinator::{consumed, map, map_res, opt}; |
5 | use nom::multi::separated_list1; |
6 | use nom::sequence::{pair, preceded, tuple}; |
7 | |
8 | use crate::{ |
9 | CharLit, ErrorContext, Num, ParseErr, ParseResult, PathOrIdentifier, State, StrLit, WithSpan, |
10 | bool_lit, char_lit, identifier, keyword, num_lit, path_or_identifier, str_lit, ws, |
11 | }; |
12 | |
13 | #[derive (Clone, Debug, PartialEq)] |
14 | pub enum Target<'a> { |
15 | Name(&'a str), |
16 | Tuple(Vec<&'a str>, Vec<Target<'a>>), |
17 | Array(Vec<&'a str>, Vec<Target<'a>>), |
18 | Struct(Vec<&'a str>, Vec<(&'a str, Target<'a>)>), |
19 | NumLit(&'a str, Num<'a>), |
20 | StrLit(StrLit<'a>), |
21 | CharLit(CharLit<'a>), |
22 | BoolLit(&'a str), |
23 | Path(Vec<&'a str>), |
24 | OrChain(Vec<Target<'a>>), |
25 | Placeholder(&'a str), |
26 | /// The `Option` is the variable name (if any) in `var_name @ ..`. |
27 | Rest(WithSpan<'a, Option<&'a str>>), |
28 | } |
29 | |
30 | impl<'a> Target<'a> { |
31 | /// Parses multiple targets with `or` separating them |
32 | pub(super) fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
33 | map( |
34 | separated_list1(ws(tag("or" )), |i| s.nest(i, |i| Self::parse_one(i, s))), |
35 | |mut opts| match opts.len() { |
36 | 1 => opts.pop().unwrap(), |
37 | _ => Self::OrChain(opts), |
38 | }, |
39 | )(i) |
40 | } |
41 | |
42 | /// Parses a single target without an `or`, unless it is wrapped in parentheses. |
43 | fn parse_one(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
44 | let mut opt_opening_paren = map(opt(ws(char('(' ))), |o| o.is_some()); |
45 | let mut opt_opening_brace = map(opt(ws(char('{' ))), |o| o.is_some()); |
46 | let mut opt_opening_bracket = map(opt(ws(char('[' ))), |o| o.is_some()); |
47 | |
48 | let (i, lit) = opt(Self::lit)(i)?; |
49 | if let Some(lit) = lit { |
50 | return Ok((i, lit)); |
51 | } |
52 | |
53 | // match tuples and unused parentheses |
54 | let (i, target_is_tuple) = opt_opening_paren(i)?; |
55 | if target_is_tuple { |
56 | let (i, (singleton, mut targets)) = collect_targets(i, s, ')' , Self::unnamed)?; |
57 | if singleton { |
58 | return Ok((i, targets.pop().unwrap())); |
59 | } |
60 | return Ok(( |
61 | i, |
62 | Self::Tuple(Vec::new(), only_one_rest_pattern(targets, false, "tuple" )?), |
63 | )); |
64 | } |
65 | let (i, target_is_array) = opt_opening_bracket(i)?; |
66 | if target_is_array { |
67 | let (i, (singleton, mut targets)) = collect_targets(i, s, ']' , Self::unnamed)?; |
68 | if singleton { |
69 | return Ok((i, targets.pop().unwrap())); |
70 | } |
71 | return Ok(( |
72 | i, |
73 | Self::Array(Vec::new(), only_one_rest_pattern(targets, true, "array" )?), |
74 | )); |
75 | } |
76 | |
77 | let path = |i| { |
78 | map_res(path_or_identifier, |v| match v { |
79 | PathOrIdentifier::Path(v) => Ok(v), |
80 | PathOrIdentifier::Identifier(v) => Err(v), |
81 | })(i) |
82 | }; |
83 | |
84 | // match structs |
85 | let (i, path) = opt(path)(i)?; |
86 | if let Some(path) = path { |
87 | let i_before_matching_with = i; |
88 | let (i, _) = opt(ws(keyword("with" )))(i)?; |
89 | |
90 | let (i, is_unnamed_struct) = opt_opening_paren(i)?; |
91 | if is_unnamed_struct { |
92 | let (i, (_, targets)) = collect_targets(i, s, ')' , Self::unnamed)?; |
93 | return Ok(( |
94 | i, |
95 | Self::Tuple(path, only_one_rest_pattern(targets, false, "struct" )?), |
96 | )); |
97 | } |
98 | |
99 | let (i, is_named_struct) = opt_opening_brace(i)?; |
100 | if is_named_struct { |
101 | let (i, (_, targets)) = collect_targets(i, s, '}' , Self::named)?; |
102 | return Ok((i, Self::Struct(path, targets))); |
103 | } |
104 | |
105 | return Ok((i_before_matching_with, Self::Path(path))); |
106 | } |
107 | |
108 | // neither literal nor struct nor path |
109 | let (new_i, name) = identifier(i)?; |
110 | let target = match name { |
111 | "_" => Self::Placeholder(name), |
112 | _ => verify_name(i, name)?, |
113 | }; |
114 | Ok((new_i, target)) |
115 | } |
116 | |
117 | fn lit(i: &'a str) -> ParseResult<'a, Self> { |
118 | alt(( |
119 | map(str_lit, Self::StrLit), |
120 | map(char_lit, Self::CharLit), |
121 | map(consumed(num_lit), |(full, num)| Target::NumLit(full, num)), |
122 | map(bool_lit, Self::BoolLit), |
123 | ))(i) |
124 | } |
125 | |
126 | fn unnamed(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
127 | alt((Self::rest, |i| Self::parse(i, s)))(i) |
128 | } |
129 | |
130 | fn named(init_i: &'a str, s: &State<'_>) -> ParseResult<'a, (&'a str, Self)> { |
131 | let (i, rest) = opt(consumed(Self::rest))(init_i)?; |
132 | if let Some(rest) = rest { |
133 | let (_, chr) = ws(opt(one_of(",:" )))(i)?; |
134 | if let Some(chr) = chr { |
135 | return Err(nom::Err::Failure(ErrorContext::new( |
136 | format!( |
137 | "unexpected ` {chr}` character after `..` \n\ |
138 | note that in a named struct, `..` must come last to ignore other members" |
139 | ), |
140 | i, |
141 | ))); |
142 | } |
143 | if let Target::Rest(ref s) = rest.1 { |
144 | if s.inner.is_some() { |
145 | return Err(nom::Err::Failure(ErrorContext::new( |
146 | "`@ ..` cannot be used in struct" , |
147 | s.span, |
148 | ))); |
149 | } |
150 | } |
151 | return Ok((i, rest)); |
152 | } |
153 | |
154 | let (i, (src, target)) = pair( |
155 | identifier, |
156 | opt(preceded(ws(char(':' )), |i| Self::parse(i, s))), |
157 | )(init_i)?; |
158 | |
159 | if src == "_" { |
160 | return Err(nom::Err::Failure(ErrorContext::new( |
161 | "cannot use placeholder `_` as source in named struct" , |
162 | init_i, |
163 | ))); |
164 | } |
165 | |
166 | let target = match target { |
167 | Some(target) => target, |
168 | None => verify_name(init_i, src)?, |
169 | }; |
170 | Ok((i, (src, target))) |
171 | } |
172 | |
173 | fn rest(start: &'a str) -> ParseResult<'a, Self> { |
174 | let (i, (ident, _)) = tuple((opt(tuple((identifier, ws(char('@' ))))), tag(".." )))(start)?; |
175 | Ok(( |
176 | i, |
177 | Self::Rest(WithSpan::new(ident.map(|(ident, _)| ident), start)), |
178 | )) |
179 | } |
180 | } |
181 | |
182 | fn verify_name<'a>( |
183 | input: &'a str, |
184 | name: &'a str, |
185 | ) -> Result<Target<'a>, nom::Err<ErrorContext<'a>>> { |
186 | match name { |
187 | "self" | "writer" => Err(nom::Err::Failure(ErrorContext::new( |
188 | message:format!("cannot use ` {name}` as a name" ), |
189 | input, |
190 | ))), |
191 | _ => Ok(Target::Name(name)), |
192 | } |
193 | } |
194 | |
195 | fn collect_targets<'a, T>( |
196 | i: &'a str, |
197 | s: &State<'_>, |
198 | delim: char, |
199 | mut one: impl FnMut(&'a str, &State<'_>) -> ParseResult<'a, T>, |
200 | ) -> ParseResult<'a, (bool, Vec<T>)> { |
201 | let opt_comma = |i| map(ws(opt(char(',' ))), |o| o.is_some())(i); |
202 | let opt_end = |i| map(ws(opt(char(delim))), |o| o.is_some())(i); |
203 | |
204 | let (i, has_end) = opt_end(i)?; |
205 | if has_end { |
206 | return Ok((i, (false, Vec::new()))); |
207 | } |
208 | |
209 | let (i, targets) = opt(separated_list1(ws(char(',' )), |i| one(i, s)))(i)?; |
210 | let Some(targets) = targets else { |
211 | return Err(nom::Err::Failure(ErrorContext::new( |
212 | "expected comma separated list of members" , |
213 | i, |
214 | ))); |
215 | }; |
216 | |
217 | let (i, (has_comma, has_end)) = pair(opt_comma, opt_end)(i)?; |
218 | if !has_end { |
219 | let msg = match has_comma { |
220 | true => format!("expected member, or ` {delim}` as terminator" ), |
221 | false => format!("expected `,` for more members, or ` {delim}` as terminator" ), |
222 | }; |
223 | return Err(nom::Err::Failure(ErrorContext::new(msg, i))); |
224 | } |
225 | |
226 | let singleton = !has_comma && targets.len() == 1; |
227 | Ok((i, (singleton, targets))) |
228 | } |
229 | |
230 | fn only_one_rest_pattern<'a>( |
231 | targets: Vec<Target<'a>>, |
232 | allow_named_rest: bool, |
233 | type_kind: &str, |
234 | ) -> Result<Vec<Target<'a>>, ParseErr<'a>> { |
235 | let mut found_rest: bool = false; |
236 | |
237 | for target: &Target<'a> in &targets { |
238 | if let Target::Rest(s: &WithSpan<'_, Option<&str>>) = target { |
239 | if !allow_named_rest && s.inner.is_some() { |
240 | return Err(nom::Err::Failure(ErrorContext::new( |
241 | message:"`@ ..` is only allowed in slice patterns" , |
242 | input:s.span, |
243 | ))); |
244 | } |
245 | if found_rest { |
246 | return Err(nom::Err::Failure(ErrorContext::new( |
247 | message:format!("`..` can only be used once per {type_kind} pattern" ), |
248 | input:s.span, |
249 | ))); |
250 | } |
251 | found_rest = true; |
252 | } |
253 | } |
254 | Ok(targets) |
255 | } |
256 | |