Skip to main content

bhc_parser/
pattern.rs

1//! Pattern parsing.
2
3use bhc_ast::{Expr, FieldPat, Lit, ModuleName, Pat};
4use bhc_intern::{Ident, Symbol};
5use bhc_lexer::TokenKind;
6use bhc_span::Span;
7
8use crate::{ParseError, ParseResult, Parser};
9
10impl<'src> Parser<'src> {
11    /// Check if the current token can start a pattern.
12    pub fn is_pattern_start(&self) -> bool {
13        match self.current_kind() {
14            Some(kind) => matches!(
15                kind,
16                TokenKind::Ident(_)
17                    | TokenKind::QualIdent(_, _)
18                    | TokenKind::ConId(_)
19                    | TokenKind::QualConId(_, _)
20                    | TokenKind::IntLit(_)
21                    | TokenKind::FloatLit(_)
22                    | TokenKind::CharLit(_)
23                    | TokenKind::StringLit(_)
24                    | TokenKind::LParen
25                    | TokenKind::LBracket
26                    | TokenKind::Underscore
27                    | TokenKind::Tilde
28                    | TokenKind::Bang
29            ),
30            None => false,
31        }
32    }
33
34    /// Parse a pattern.
35    pub fn parse_pattern(&mut self) -> ParseResult<Pat> {
36        self.enter_recursion()?;
37        let result = self.parse_infix_pattern();
38        self.exit_recursion();
39        result
40    }
41
42    /// Parse an infix pattern like `x : xs` or `x :| xs`.
43    fn parse_infix_pattern(&mut self) -> ParseResult<Pat> {
44        let mut pat = self.parse_app_pattern()?;
45
46        while let Some(tok) = self.current() {
47            match &tok.node.kind {
48                // Constructor operators like `:`, `:|` are valid in infix patterns
49                TokenKind::ConOperator(sym) => {
50                    let op = Ident::new(*sym);
51                    self.advance();
52                    let rhs = self.parse_infix_pattern()?;
53                    let span = pat.span().to(rhs.span());
54                    pat = Pat::Infix(Box::new(pat), op, Box::new(rhs), span);
55                }
56                // Qualified constructor operators like `Seq.:>` in infix patterns
57                TokenKind::QualConOperator(qual, sym) => {
58                    let qualified_name =
59                        Symbol::intern(&format!("{}.{}", qual.as_str(), sym.as_str()));
60                    let op = Ident::new(qualified_name);
61                    self.advance();
62                    let rhs = self.parse_infix_pattern()?;
63                    let span = pat.span().to(rhs.span());
64                    pat = Pat::Infix(Box::new(pat), op, Box::new(rhs), span);
65                }
66                _ => break,
67            }
68        }
69
70        Ok(pat)
71    }
72
73    /// Parse an application pattern like `Just x` or `W.Workspace i l ms`.
74    fn parse_app_pattern(&mut self) -> ParseResult<Pat> {
75        let first = self.parse_atom_pattern()?;
76
77        // Check for constructor application
78        match first {
79            Pat::Con(con, args, span) if args.is_empty() => {
80                let mut new_args = Vec::new();
81                while self.is_apat_start() {
82                    new_args.push(self.parse_atom_pattern()?);
83                }
84                if new_args.is_empty() {
85                    return Ok(Pat::Con(con, args, span));
86                }
87                let new_span = span.to(new_args.last().unwrap().span());
88                return Ok(Pat::Con(con, new_args, new_span));
89            }
90            Pat::Con(con, args, span) => {
91                return Ok(Pat::Con(con, args, span));
92            }
93            Pat::QualCon(module_name, con, args, span) if args.is_empty() => {
94                // Qualified constructor application like W.Workspace i l ms
95                let mut new_args = Vec::new();
96                while self.is_apat_start() {
97                    new_args.push(self.parse_atom_pattern()?);
98                }
99                if new_args.is_empty() {
100                    return Ok(Pat::QualCon(module_name, con, args, span));
101                }
102                let new_span = span.to(new_args.last().unwrap().span());
103                return Ok(Pat::QualCon(module_name, con, new_args, new_span));
104            }
105            Pat::QualCon(module_name, con, args, span) => {
106                return Ok(Pat::QualCon(module_name, con, args, span));
107            }
108            _ => {}
109        }
110
111        Ok(first)
112    }
113
114    /// Check if current token can start an atomic pattern.
115    pub fn is_apat_start(&self) -> bool {
116        match self.current_kind() {
117            Some(kind) => matches!(
118                kind,
119                TokenKind::Ident(_)
120                    | TokenKind::ConId(_)        // Constructors can be pattern arguments
121                    | TokenKind::QualConId(_, _) // Qualified constructors too
122                    | TokenKind::IntLit(_)
123                    | TokenKind::FloatLit(_)
124                    | TokenKind::CharLit(_)
125                    | TokenKind::StringLit(_)
126                    | TokenKind::LParen
127                    | TokenKind::LBracket
128                    | TokenKind::Underscore
129                    | TokenKind::Tilde        // Lazy pattern ~x
130                    | TokenKind::Bang // Strict pattern !x
131            ),
132            None => false,
133        }
134    }
135
136    /// Parse an atomic pattern.
137    /// This is used for function argument patterns in clause LHS.
138    pub fn parse_atom_pattern(&mut self) -> ParseResult<Pat> {
139        let tok = self.current().ok_or(ParseError::UnexpectedEof {
140            expected: "pattern".to_string(),
141        })?;
142
143        match &tok.node.kind.clone() {
144            TokenKind::Underscore => {
145                let span = tok.span;
146                self.advance();
147                Ok(Pat::Wildcard(span))
148            }
149
150            TokenKind::Ident(sym) => {
151                let ident = Ident::new(*sym);
152                let span = tok.span;
153                self.advance();
154
155                // Check for as-pattern: x@pat
156                if self.eat(&TokenKind::At) {
157                    let pat = self.parse_atom_pattern()?;
158                    let new_span = span.to(pat.span());
159                    Ok(Pat::As(ident, Box::new(pat), new_span))
160                } else {
161                    Ok(Pat::Var(ident, span))
162                }
163            }
164
165            TokenKind::QualIdent(qual, name) => {
166                // Qualified identifier like M.x - treat as variable
167                let full_name = format!("{}.{}", qual.as_str(), name.as_str());
168                let ident = Ident::from_str(&full_name);
169                let span = tok.span;
170                self.advance();
171                Ok(Pat::Var(ident, span))
172            }
173
174            TokenKind::ConId(sym) => {
175                let ident = Ident::new(*sym);
176                let span = tok.span;
177                self.advance();
178
179                // Check for record pattern: Con { field = pat, ... }
180                if self.check(&TokenKind::LBrace) {
181                    return self.parse_record_pattern(ident, span);
182                }
183
184                Ok(Pat::Con(ident, vec![], span))
185            }
186
187            TokenKind::QualConId(qual, name) => {
188                // Qualified constructor like W.RationalRect
189                let module_name = ModuleName {
190                    parts: vec![*qual],
191                    span: tok.span,
192                };
193                let ident = Ident::new(*name);
194                let span = tok.span;
195                self.advance();
196
197                // Check for record pattern: Qual.Con { field = pat, ... }
198                if self.check(&TokenKind::LBrace) {
199                    return self.parse_qual_record_pattern(module_name, ident, span);
200                }
201
202                Ok(Pat::QualCon(module_name, ident, vec![], span))
203            }
204
205            TokenKind::IntLit(ref lit) => {
206                let span = tok.span;
207                let value = self.parse_int_literal(&lit.text, span)?;
208                self.advance();
209                Ok(Pat::Lit(Lit::Int(value), span))
210            }
211
212            TokenKind::FloatLit(ref lit) => {
213                let span = tok.span;
214                let value = self.parse_float_literal(&lit.text, span)?;
215                self.advance();
216                Ok(Pat::Lit(Lit::Float(value), span))
217            }
218
219            TokenKind::CharLit(c) => {
220                let span = tok.span;
221                let c = *c;
222                self.advance();
223                Ok(Pat::Lit(Lit::Char(c), span))
224            }
225
226            TokenKind::StringLit(s) => {
227                let span = tok.span;
228                let s = s.clone();
229                self.advance();
230                Ok(Pat::Lit(Lit::String(s), span))
231            }
232
233            TokenKind::LParen => self.parse_paren_pattern(),
234
235            TokenKind::LBracket => self.parse_list_pattern(),
236
237            TokenKind::Tilde => {
238                let start = tok.span;
239                self.advance();
240                let pat = self.parse_atom_pattern()?;
241                let span = start.to(pat.span());
242                Ok(Pat::Lazy(Box::new(pat), span))
243            }
244
245            TokenKind::Bang => {
246                let start = tok.span;
247                self.advance();
248                let pat = self.parse_atom_pattern()?;
249                let span = start.to(pat.span());
250                Ok(Pat::Bang(Box::new(pat), span))
251            }
252
253            _ => Err(ParseError::Unexpected {
254                found: tok.node.kind.description().to_string(),
255                expected: "pattern".to_string(),
256                span: tok.span,
257            }),
258        }
259    }
260
261    /// Parse a parenthesized pattern, tuple pattern, or view pattern.
262    fn parse_paren_pattern(&mut self) -> ParseResult<Pat> {
263        let start = self.current_span();
264        self.expect(&TokenKind::LParen)?;
265
266        if self.eat(&TokenKind::RParen) {
267            // Unit pattern: ()
268            let span = start.to(self.tokens[self.pos - 1].span);
269            return Ok(Pat::Con(Ident::from_str("()"), vec![], span));
270        }
271
272        let first = self.parse_pattern()?;
273
274        // Check for view pattern: (expr -> pat)
275        // ViewPatterns extension syntax
276        if self.eat(&TokenKind::Arrow) {
277            // Convert the pattern to an expression for the view function
278            let view_expr = self.pat_to_expr(&first)?;
279            let result_pat = self.parse_pattern()?;
280            let end = self.expect(&TokenKind::RParen)?;
281            let span = start.to(end.span);
282            return Ok(Pat::View(Box::new(view_expr), Box::new(result_pat), span));
283        }
284
285        // View pattern with function application: (f x y -> pat)
286        // If the first element is a variable (not a constructor) and the next
287        // token could be a function argument (not a separator/closer), try
288        // parsing as an applied view expression.
289        if matches!(&first, Pat::Var(..)) && self.is_apat_start() {
290            // Speculatively try to parse as view pattern with args
291            let save_pos = self.pos;
292            let mut args: Vec<Pat> = Vec::new();
293            while self.is_apat_start() && !self.check(&TokenKind::Arrow) {
294                args.push(self.parse_atom_pattern()?);
295            }
296            if self.eat(&TokenKind::Arrow) {
297                // It's a view pattern: build (f arg1 arg2 ... -> resultPat)
298                let mut view_expr = self.pat_to_expr(&first)?;
299                for arg in &args {
300                    let arg_expr = self.pat_to_expr(arg)?;
301                    let new_span = view_expr.span().to(arg_expr.span());
302                    view_expr = Expr::App(Box::new(view_expr), Box::new(arg_expr), new_span);
303                }
304                let result_pat = self.parse_pattern()?;
305                let end = self.expect(&TokenKind::RParen)?;
306                let span = start.to(end.span);
307                return Ok(Pat::View(Box::new(view_expr), Box::new(result_pat), span));
308            }
309            // Not a view pattern — backtrack
310            self.pos = save_pos;
311        }
312
313        // Check for pattern type signature: (pat :: Type)
314        if self.eat(&TokenKind::DoubleColon) {
315            let ty = self.parse_type()?;
316            let end = self.expect(&TokenKind::RParen)?;
317            let span = start.to(end.span);
318            return Ok(Pat::Ann(Box::new(first), ty, span));
319        }
320
321        if self.eat(&TokenKind::Comma) {
322            // Tuple pattern
323            let mut pats = vec![first];
324            loop {
325                pats.push(self.parse_pattern()?);
326                if !self.eat(&TokenKind::Comma) {
327                    break;
328                }
329            }
330            let end = self.expect(&TokenKind::RParen)?;
331            let span = start.to(end.span);
332            Ok(Pat::Tuple(pats, span))
333        } else {
334            // Parenthesized pattern
335            let end = self.expect(&TokenKind::RParen)?;
336            let span = start.to(end.span);
337            Ok(Pat::Paren(Box::new(first), span))
338        }
339    }
340
341    /// Convert a pattern to an expression (for view patterns).
342    /// This handles the common case where the "pattern" before -> is actually a function.
343    fn pat_to_expr(&self, pat: &Pat) -> ParseResult<Expr> {
344        use bhc_ast::Expr;
345        match pat {
346            Pat::Var(name, span) => {
347                // Check if this is a qualified variable (e.g. "L.reverse" from QualIdent token)
348                let name_str = name.name.as_str();
349                if let Some(dot_pos) = name_str.rfind('.') {
350                    let qualifier = &name_str[..dot_pos];
351                    let local = &name_str[dot_pos + 1..];
352                    if !qualifier.is_empty() && !local.is_empty() {
353                        let module_name = ModuleName {
354                            parts: vec![Symbol::intern(qualifier)],
355                            span: *span,
356                        };
357                        let local_ident = Ident::from_str(local);
358                        return Ok(Expr::QualVar(module_name, local_ident, *span));
359                    }
360                }
361                Ok(Expr::Var(*name, *span))
362            }
363            Pat::Con(name, args, span) => {
364                if args.is_empty() {
365                    Ok(Expr::Con(*name, *span))
366                } else {
367                    // Constructor application: Con a b -> App (App Con a) b
368                    let mut result = Expr::Con(*name, *span);
369                    for arg in args {
370                        let arg_expr = self.pat_to_expr(arg)?;
371                        let new_span = result.span().to(arg_expr.span());
372                        result = Expr::App(Box::new(result), Box::new(arg_expr), new_span);
373                    }
374                    Ok(result)
375                }
376            }
377            Pat::QualCon(module_name, name, args, span) => {
378                if args.is_empty() {
379                    Ok(Expr::QualCon(module_name.clone(), *name, *span))
380                } else {
381                    // Constructor application: Mod.Con a b -> App (App Mod.Con a) b
382                    let mut result = Expr::QualCon(module_name.clone(), *name, *span);
383                    for arg in args {
384                        let arg_expr = self.pat_to_expr(arg)?;
385                        let new_span = result.span().to(arg_expr.span());
386                        result = Expr::App(Box::new(result), Box::new(arg_expr), new_span);
387                    }
388                    Ok(result)
389                }
390            }
391            Pat::Lit(lit, span) => Ok(Expr::Lit(lit.clone(), *span)),
392            Pat::Paren(inner, span) => {
393                let inner_expr = self.pat_to_expr(inner)?;
394                Ok(Expr::Paren(Box::new(inner_expr), *span))
395            }
396            Pat::Tuple(elems, span) => {
397                let mut exprs = Vec::new();
398                for elem in elems {
399                    exprs.push(self.pat_to_expr(elem)?);
400                }
401                Ok(Expr::Tuple(exprs, *span))
402            }
403            Pat::List(elems, span) => {
404                let mut exprs = Vec::new();
405                for elem in elems {
406                    exprs.push(self.pat_to_expr(elem)?);
407                }
408                Ok(Expr::List(exprs, *span))
409            }
410            Pat::Wildcard(span) => {
411                // Wildcard in view expression context — shouldn't happen, but handle gracefully
412                Ok(Expr::Var(Ident::from_str("_"), *span))
413            }
414            _ => Err(ParseError::Unexpected {
415                found: "complex pattern".to_string(),
416                expected: "simple expression for view pattern".to_string(),
417                span: pat.span(),
418            }),
419        }
420    }
421
422    /// Parse a list pattern.
423    fn parse_list_pattern(&mut self) -> ParseResult<Pat> {
424        let start = self.current_span();
425        self.expect(&TokenKind::LBracket)?;
426
427        if self.eat(&TokenKind::RBracket) {
428            // Empty list: []
429            let span = start.to(self.tokens[self.pos - 1].span);
430            return Ok(Pat::List(vec![], span));
431        }
432
433        let mut pats = vec![self.parse_pattern()?];
434        while self.eat(&TokenKind::Comma) {
435            if self.check(&TokenKind::RBracket) {
436                break;
437            }
438            pats.push(self.parse_pattern()?);
439        }
440
441        let end = self.expect(&TokenKind::RBracket)?;
442        let span = start.to(end.span);
443        Ok(Pat::List(pats, span))
444    }
445
446    /// Parse a record pattern: `Con { field = pat, ... }` or `Con { field = pat, .. }`
447    fn parse_record_pattern(&mut self, con: Ident, start: Span) -> ParseResult<Pat> {
448        self.expect(&TokenKind::LBrace)?;
449
450        let mut fields = Vec::new();
451        let mut has_wildcard = false;
452        if !self.check(&TokenKind::RBrace) {
453            if self.eat(&TokenKind::DotDot) {
454                has_wildcard = true;
455            } else {
456                fields.push(self.parse_field_pat()?);
457                while self.eat(&TokenKind::Comma) {
458                    if self.check(&TokenKind::RBrace) {
459                        break;
460                    }
461                    if self.eat(&TokenKind::DotDot) {
462                        has_wildcard = true;
463                        break;
464                    }
465                    fields.push(self.parse_field_pat()?);
466                }
467            }
468        }
469
470        let end = self.expect(&TokenKind::RBrace)?;
471        let span = start.to(end.span);
472        Ok(Pat::Record(con, fields, has_wildcard, span))
473    }
474
475    /// Parse a qualified record pattern: `Qual.Con { field = pat, ... }` or `Qual.Con { .. }`
476    fn parse_qual_record_pattern(
477        &mut self,
478        module_name: ModuleName,
479        con: Ident,
480        start: Span,
481    ) -> ParseResult<Pat> {
482        self.expect(&TokenKind::LBrace)?;
483
484        let mut fields = Vec::new();
485        let mut has_wildcard = false;
486        if !self.check(&TokenKind::RBrace) {
487            if self.eat(&TokenKind::DotDot) {
488                has_wildcard = true;
489            } else {
490                fields.push(self.parse_field_pat()?);
491                while self.eat(&TokenKind::Comma) {
492                    if self.check(&TokenKind::RBrace) {
493                        break;
494                    }
495                    if self.eat(&TokenKind::DotDot) {
496                        has_wildcard = true;
497                        break;
498                    }
499                    fields.push(self.parse_field_pat()?);
500                }
501            }
502        }
503
504        let end = self.expect(&TokenKind::RBrace)?;
505        let span = start.to(end.span);
506        Ok(Pat::QualRecord(
507            module_name,
508            con,
509            fields,
510            has_wildcard,
511            span,
512        ))
513    }
514
515    /// Parse a field pattern: `field = pat`, `Mod.field = pat`, or `field` (punning)
516    fn parse_field_pat(&mut self) -> ParseResult<FieldPat> {
517        let tok = self.current().ok_or(ParseError::UnexpectedEof {
518            expected: "field name".to_string(),
519        })?;
520
521        let (qualifier, name, span) = match &tok.node.kind {
522            TokenKind::Ident(sym) => (None, Ident::new(*sym), tok.span),
523            TokenKind::QualIdent(qual, sym) => {
524                let module_name = ModuleName {
525                    parts: vec![*qual],
526                    span: tok.span,
527                };
528                (Some(module_name), Ident::new(*sym), tok.span)
529            }
530            _ => {
531                return Err(ParseError::Unexpected {
532                    found: tok.node.kind.description().to_string(),
533                    expected: "field name".to_string(),
534                    span: tok.span,
535                });
536            }
537        };
538        self.advance();
539
540        let pat = if self.eat(&TokenKind::Eq) {
541            Some(self.parse_pattern()?)
542        } else {
543            None // Punning: `Foo { bar }` means `Foo { bar = bar }`
544        };
545
546        let end_span = pat.as_ref().map(|p| p.span()).unwrap_or(span);
547        let full_span = span.to(end_span);
548        Ok(FieldPat {
549            qualifier,
550            name,
551            pat,
552            span: full_span,
553        })
554    }
555}