Skip to main content

shape_ast/parser/expressions/control_flow/
pattern_matching.rs

1//! Pattern matching expression parsing
2//!
3//! This module handles parsing of pattern matching expressions:
4//! - Match expressions
5//! - Pattern parsing (wildcard, identifier, literal, array, object, constructor)
6
7use crate::ast::{Expr, MatchArm, MatchExpr, Pattern};
8use crate::error::{Result, ShapeError};
9use crate::parser::Rule;
10use pest::iterators::Pair;
11
12use super::super::super::pair_span;
13use crate::parser::pair_location;
14
15/// Parse match expression
16pub fn parse_match_expr(pair: Pair<Rule>) -> Result<Expr> {
17    let span = pair_span(&pair);
18    let pair_loc = pair_location(&pair);
19    let mut inner = pair.into_inner();
20
21    let scrutinee_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
22        message: "expected expression to match against".to_string(),
23        location: Some(pair_loc),
24    })?;
25    let scrutinee = parse_match_scrutinee(scrutinee_pair)?;
26    let mut arms = Vec::new();
27
28    for arm_pair in inner {
29        if arm_pair.as_rule() == Rule::match_arm {
30            let arm_inner_pairs: Vec<_> = arm_pair.into_inner().collect();
31            let pattern_span = Some(pair_span(&arm_inner_pairs[0]));
32            let pattern = parse_pattern(arm_inner_pairs[0].clone())?;
33
34            let mut guard = None;
35            let mut body = None;
36
37            // Process remaining pairs
38            for i in 1..arm_inner_pairs.len() {
39                let next = &arm_inner_pairs[i];
40                if next.as_rule() == Rule::expression {
41                    if body.is_none() && guard.is_some() {
42                        body = Some(super::super::parse_expression(next.clone())?);
43                    } else if guard.is_none() {
44                        // This might be a guard or the body
45                        if i < arm_inner_pairs.len() - 1 {
46                            guard = Some(Box::new(super::super::parse_expression(next.clone())?));
47                        } else {
48                            body = Some(super::super::parse_expression(next.clone())?);
49                        }
50                    }
51                }
52            }
53
54            let body = body.ok_or_else(|| ShapeError::ParseError {
55                message: "Match arm missing body".to_string(),
56                location: None,
57            })?;
58
59            arms.push(MatchArm {
60                pattern,
61                guard,
62                body: Box::new(body),
63                pattern_span,
64            });
65        }
66    }
67
68    Ok(Expr::Match(
69        Box::new(MatchExpr {
70            scrutinee: Box::new(scrutinee),
71            arms,
72        }),
73        span,
74    ))
75}
76
77fn parse_match_scrutinee(pair: Pair<Rule>) -> Result<Expr> {
78    let pair_loc = pair_location(&pair);
79    match pair.as_rule() {
80        Rule::match_scrutinee => {
81            let inner = pair
82                .into_inner()
83                .next()
84                .ok_or_else(|| ShapeError::ParseError {
85                    message: "expected match scrutinee".to_string(),
86                    location: Some(pair_loc),
87                })?;
88            parse_match_scrutinee(inner)
89        }
90        Rule::match_scrutinee_ident => {
91            let ident_pair = pair
92                .into_inner()
93                .next()
94                .ok_or_else(|| ShapeError::ParseError {
95                    message: "expected identifier in match scrutinee".to_string(),
96                    location: Some(pair_loc),
97                })?;
98            Ok(Expr::Identifier(
99                ident_pair.as_str().to_string(),
100                pair_span(&ident_pair),
101            ))
102        }
103        Rule::ident => Ok(Expr::Identifier(
104            pair.as_str().to_string(),
105            pair_span(&pair),
106        )),
107        // WS-4 4c: a bare struct-literal scrutinee
108        // (`match Point { x: 3, y: 4 } { … }`) parses to a
109        // `match_scrutinee_struct` wrapper around a `struct_literal`,
110        // which `parse_expression` does not accept directly — route the
111        // inner `struct_literal` through `parse_primary_expr`.
112        Rule::match_scrutinee_struct => {
113            let inner = pair
114                .into_inner()
115                .next()
116                .ok_or_else(|| ShapeError::ParseError {
117                    message: "expected struct literal in match scrutinee".to_string(),
118                    location: Some(pair_loc),
119                })?;
120            super::super::parse_primary_expr(inner)
121        }
122        Rule::struct_literal => super::super::parse_primary_expr(pair),
123        Rule::expression => super::super::parse_expression(pair),
124        _ => super::super::parse_expression(pair),
125    }
126}
127
128/// Parse pattern
129pub fn parse_pattern(pair: Pair<Rule>) -> Result<Pattern> {
130    let pair_loc = pair_location(&pair);
131    match pair.as_rule() {
132        Rule::pattern => {
133            let inner = pair
134                .into_inner()
135                .next()
136                .ok_or_else(|| ShapeError::ParseError {
137                    message: "expected pattern content".to_string(),
138                    location: Some(pair_loc),
139                })?;
140            parse_pattern(inner)
141        }
142        Rule::pattern_wildcard => Ok(Pattern::Wildcard),
143        Rule::pattern_typed => {
144            let mut inner = pair.into_inner();
145            let name = inner
146                .next()
147                .ok_or_else(|| ShapeError::ParseError {
148                    message: "expected identifier in typed pattern".to_string(),
149                    location: Some(pair_loc.clone()),
150                })?
151                .as_str()
152                .to_string();
153            let type_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
154                message: "expected type annotation in typed pattern".to_string(),
155                location: Some(pair_loc),
156            })?;
157            let type_annotation = crate::parser::parse_type_annotation(type_pair)?;
158            Ok(Pattern::Typed {
159                name,
160                type_annotation,
161            })
162        }
163        Rule::pattern_identifier => Ok(Pattern::Identifier(pair.as_str().to_string())),
164        Rule::pattern_literal => {
165            let literal_pair = pair
166                .into_inner()
167                .next()
168                .ok_or_else(|| ShapeError::ParseError {
169                    message: "expected literal in pattern".to_string(),
170                    location: Some(pair_loc.clone()),
171                })?;
172            let literal = super::super::literals::parse_literal(literal_pair)?;
173            match literal {
174                Expr::Literal(lit, _) => Ok(Pattern::Literal(lit)),
175                _ => Err(ShapeError::ParseError {
176                    message: "expected literal in pattern".to_string(),
177                    location: Some(pair_loc),
178                }),
179            }
180        }
181        Rule::pattern_array => {
182            let mut patterns = Vec::new();
183            for inner in pair.into_inner() {
184                patterns.push(parse_pattern(inner)?);
185            }
186            Ok(Pattern::Array(patterns))
187        }
188        Rule::pattern_object => {
189            let mut fields = Vec::new();
190            for field in pair.into_inner() {
191                if field.as_rule() == Rule::pattern_field {
192                    let mut field_inner = field.into_inner();
193                    let name = field_inner
194                        .next()
195                        .ok_or_else(|| ShapeError::ParseError {
196                            message: "expected field name in object pattern".to_string(),
197                            location: Some(pair_loc.clone()),
198                        })?
199                        .as_str()
200                        .to_string();
201                    // Shorthand: `{x, y}` is equivalent to `{x: x, y: y}`
202                    let pattern = if let Some(pattern_pair) = field_inner.next() {
203                        parse_pattern(pattern_pair)?
204                    } else {
205                        Pattern::Identifier(name.clone())
206                    };
207                    fields.push((name, pattern));
208                }
209            }
210            Ok(Pattern::Object(fields))
211        }
212        Rule::pattern_constructor => {
213            let mut inner = pair.into_inner();
214            let ctor_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
215                message: "expected constructor pattern".to_string(),
216                location: Some(pair_loc.clone()),
217            })?;
218            parse_constructor_pattern(ctor_pair)
219        }
220        _ => Err(ShapeError::ParseError {
221            message: format!("unexpected pattern rule: {:?}", pair.as_rule()),
222            location: Some(pair_loc),
223        }),
224    }
225}
226
227fn parse_constructor_pattern(pair: Pair<Rule>) -> Result<Pattern> {
228    let pair_loc = pair_location(&pair);
229    match pair.as_rule() {
230        Rule::pattern_qualified_constructor => {
231            let inner = pair.into_inner();
232            let mut ident_segments = Vec::new();
233            let mut payload_pair = None;
234            for child in inner {
235                match child.as_rule() {
236                    Rule::ident | Rule::variant_ident => {
237                        ident_segments.push(child.as_str().to_string())
238                    }
239                    Rule::pattern_constructor_payload => payload_pair = Some(child),
240                    _ => {}
241                }
242            }
243            if ident_segments.len() < 2 {
244                return Err(ShapeError::ParseError {
245                    message: "expected Enum::Variant in constructor pattern".to_string(),
246                    location: Some(pair_loc),
247                });
248            }
249            let variant = ident_segments.pop().unwrap();
250            let enum_path = if ident_segments.len() == 1 {
251                crate::ast::TypePath::simple(ident_segments.remove(0))
252            } else {
253                crate::ast::TypePath::from_segments(ident_segments)
254            };
255            let fields = if let Some(payload) = payload_pair {
256                parse_constructor_payload(payload)?
257            } else {
258                crate::ast::PatternConstructorFields::Unit
259            };
260            Ok(Pattern::Constructor {
261                enum_name: Some(enum_path),
262                variant,
263                fields,
264            })
265        }
266        Rule::pattern_unqualified_constructor => {
267            let mut inner = pair.into_inner();
268            let name_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
269                message: "expected constructor name in pattern".to_string(),
270                location: Some(pair_loc.clone()),
271            })?;
272            let variant = match name_pair.as_rule() {
273                Rule::pattern_constructor_name => name_pair
274                    .clone()
275                    .into_inner()
276                    .next()
277                    .map(|p| p.as_str().to_string())
278                    .unwrap_or_else(|| name_pair.as_str().to_string()),
279                Rule::pattern_constructor_keyword => name_pair.as_str().to_string(),
280                _ => name_pair.as_str().to_string(),
281            };
282            let fields = if let Some(payload_pair) = inner.next() {
283                parse_constructor_payload(payload_pair)?
284            } else {
285                crate::ast::PatternConstructorFields::Unit
286            };
287            Ok(Pattern::Constructor {
288                enum_name: None,
289                variant,
290                fields,
291            })
292        }
293        _ => Err(ShapeError::ParseError {
294            message: format!("unexpected constructor pattern rule: {:?}", pair.as_rule()),
295            location: Some(pair_loc),
296        }),
297    }
298}
299
300fn parse_constructor_payload(pair: Pair<Rule>) -> Result<crate::ast::PatternConstructorFields> {
301    let pair_loc = pair_location(&pair);
302    match pair.as_rule() {
303        Rule::pattern_constructor_payload => {
304            let inner = pair
305                .into_inner()
306                .next()
307                .ok_or_else(|| ShapeError::ParseError {
308                    message: "expected constructor payload".to_string(),
309                    location: Some(pair_loc),
310                })?;
311            parse_constructor_payload(inner)
312        }
313        Rule::pattern_constructor_tuple => {
314            let mut patterns = Vec::new();
315            for inner in pair.into_inner() {
316                patterns.push(parse_pattern(inner)?);
317            }
318            Ok(crate::ast::PatternConstructorFields::Tuple(patterns))
319        }
320        Rule::pattern_constructor_struct => {
321            let mut fields = Vec::new();
322            for field in pair.into_inner() {
323                if field.as_rule() == Rule::pattern_field {
324                    let field_loc = pair_location(&field);
325                    let mut field_inner = field.into_inner();
326                    let name = field_inner
327                        .next()
328                        .ok_or_else(|| ShapeError::ParseError {
329                            message: "expected field name in constructor pattern".to_string(),
330                            location: Some(field_loc.clone()),
331                        })?
332                        .as_str()
333                        .to_string();
334                    let pattern = if let Some(pattern_pair) = field_inner.next() {
335                        parse_pattern(pattern_pair)?
336                    } else {
337                        // Shorthand: { radius } == { radius: radius }
338                        crate::ast::Pattern::Identifier(name.clone())
339                    };
340                    fields.push((name, pattern));
341                }
342            }
343            Ok(crate::ast::PatternConstructorFields::Struct(fields))
344        }
345        _ => Err(ShapeError::ParseError {
346            message: format!("unexpected constructor payload rule: {:?}", pair.as_rule()),
347            location: Some(pair_loc),
348        }),
349    }
350}