use crate::literal::Literal;
use crate::node::Spanned;
use crate::pattern::Pattern;
use crate::token::Token;
use super::{ParseResult, Parser};
pub fn parse_pattern(p: &mut Parser) -> ParseResult<Spanned<Pattern>> {
let start = p.current_pos();
let pat = parse_cons_pattern(p)?;
p.skip_whitespace();
if matches!(p.peek(), Token::As) {
p.advance(); let name = p.expect_lower_name()?;
let result = Pattern::As {
pattern: Box::new(pat),
name,
};
Ok(p.spanned_from(start, result))
} else {
Ok(pat)
}
}
fn parse_cons_pattern(p: &mut Parser) -> ParseResult<Spanned<Pattern>> {
let start = p.current_pos();
let left = parse_app_pattern(p)?;
p.skip_whitespace();
if matches!(p.peek(), Token::Operator(op) if op == "::") {
p.advance(); let right = parse_cons_pattern(p)?; let result = Pattern::Cons {
head: Box::new(left),
tail: Box::new(right),
};
Ok(p.spanned_from(start, result))
} else {
Ok(left)
}
}
fn parse_app_pattern(p: &mut Parser) -> ParseResult<Spanned<Pattern>> {
let start = p.current_pos();
p.skip_whitespace();
if matches!(p.peek(), Token::UpperName(_)) {
let ctor_start = p.current_pos();
let (module_name, ctor_name) = parse_qualified_ctor(p)?;
let mut args = Vec::new();
loop {
p.skip_whitespace();
if !can_start_atomic_pattern(p.peek()) {
break;
}
if !p.in_paren_context()
&& p.current_column() <= ctor_start.column
&& p.current_pos().line != ctor_start.line
{
break;
}
args.push(parse_atomic_pattern(p)?);
}
let result = Pattern::Constructor {
module_name,
name: ctor_name,
args,
};
Ok(p.spanned_from(start, result))
} else {
parse_atomic_pattern(p)
}
}
fn parse_atomic_pattern(p: &mut Parser) -> ParseResult<Spanned<Pattern>> {
p.skip_whitespace();
let start = p.current_pos();
match p.peek().clone() {
Token::Underscore => {
p.advance();
Ok(p.spanned_from(start, Pattern::Anything))
}
Token::LowerName(name) => {
p.advance();
Ok(p.spanned_from(start, Pattern::Var(name)))
}
Token::UpperName(_) => {
let (module_name, name) = parse_qualified_ctor(p)?;
Ok(p.spanned_from(
start,
Pattern::Constructor {
module_name,
name,
args: Vec::new(),
},
))
}
Token::Literal(lit) => {
p.advance();
match lit {
Literal::Hex(n) => Ok(p.spanned_from(start, Pattern::Hex(n))),
_ => Ok(p.spanned_from(start, Pattern::Literal(lit))),
}
}
Token::Minus => {
p.advance();
p.skip_whitespace();
match p.peek().clone() {
Token::Literal(Literal::Int(n)) => {
p.advance();
Ok(p.spanned_from(start, Pattern::Literal(Literal::Int(-n))))
}
Token::Literal(Literal::Float(n)) => {
p.advance();
Ok(p.spanned_from(start, Pattern::Literal(Literal::Float(-n))))
}
_ => Err(p.error("expected number after `-` in pattern")),
}
}
Token::LeftParen => {
p.advance(); p.skip_whitespace();
if matches!(p.peek(), Token::RightParen) {
p.advance();
return Ok(p.spanned_from(start, Pattern::Unit));
}
let first = parse_pattern(p)?;
p.skip_whitespace();
match p.peek() {
Token::Comma => {
let mut elements = vec![first];
while p.eat(&Token::Comma) {
elements.push(parse_pattern(p)?);
}
p.expect(&Token::RightParen)?;
Ok(p.spanned_from(start, Pattern::Tuple(elements)))
}
Token::RightParen => {
p.advance();
Ok(p.spanned_from(start, Pattern::Parenthesized(Box::new(first))))
}
_ => Err(p.error("expected `,` or `)` in pattern")),
}
}
Token::LeftBrace => {
p.advance(); let mut fields = Vec::new();
if !matches!(p.peek_past_whitespace(), Token::RightBrace) {
fields.push(p.expect_lower_name()?);
while p.eat(&Token::Comma) {
fields.push(p.expect_lower_name()?);
}
}
p.expect(&Token::RightBrace)?;
Ok(p.spanned_from(start, Pattern::Record(fields)))
}
Token::LeftBracket => {
p.advance(); p.skip_whitespace();
let mut elements = Vec::new();
if !matches!(p.peek(), Token::RightBracket) {
elements.push(parse_pattern(p)?);
while p.eat(&Token::Comma) {
elements.push(parse_pattern(p)?);
}
}
p.expect(&Token::RightBracket)?;
Ok(p.spanned_from(start, Pattern::List(elements)))
}
_ => Err(p.error(format!(
"expected pattern, found {}",
super::describe(p.peek())
))),
}
}
fn parse_qualified_ctor(p: &mut Parser) -> ParseResult<(Vec<String>, String)> {
let first = p.expect_upper_name()?;
let mut parts = vec![first.value];
while matches!(p.peek(), Token::Dot) {
if !matches!(p.peek_nth_past_whitespace(1), Token::UpperName(_)) {
break;
}
let dot_pos = p.pos;
p.advance(); match p.peek() {
Token::UpperName(_) => {
let name = p.expect_upper_name()?;
parts.push(name.value);
}
_ => {
p.pos = dot_pos;
break;
}
}
}
let name = parts.pop().unwrap();
Ok((parts, name))
}
fn can_start_atomic_pattern(tok: &Token) -> bool {
matches!(
tok,
Token::Underscore
| Token::LowerName(_)
| Token::UpperName(_)
| Token::Literal(_)
| Token::Minus
| Token::LeftParen
| Token::LeftBrace
| Token::LeftBracket
)
}