use super::model::ArmLabel;
use super::reader::match_brace;
use super::scan::Token;
#[derive(Clone, Copy)]
pub(super) struct Arm {
pub(super) pattern_start: usize,
pub(super) body_start: usize,
pub(super) body_end: usize,
}
pub(super) fn split_arms(tokens: &[Token], start: usize, end: usize) -> Vec<Arm> {
let mut arms = Vec::new();
let mut cursor = start;
while cursor < end {
let Some(arrow) = next_top_level_arrow(tokens, cursor, end) else {
break;
};
let pattern_start = cursor;
let body_start = arrow + 1;
let (body_start, body_end, next) = bound_body(tokens, body_start, end);
arms.push(Arm {
pattern_start,
body_start,
body_end,
});
if next <= cursor {
break;
}
cursor = next;
}
arms
}
fn bound_body(tokens: &[Token], body_start: usize, end: usize) -> (usize, usize, usize) {
if matches!(tokens.get(body_start), Some(Token::OpenBrace)) {
if let Some(close) = match_brace(tokens, body_start, end) {
return (body_start + 1, close, close + 1);
}
return (body_start + 1, end, end);
}
match next_top_level_arrow(tokens, body_start, end) {
Some(next_arrow) => {
let next_pattern = pattern_start_before(tokens, body_start, next_arrow);
(body_start, next_pattern, next_pattern)
}
None => (body_start, end, end),
}
}
fn next_top_level_arrow(tokens: &[Token], from: usize, end: usize) -> Option<usize> {
let mut depth = 0_i32;
let upper = end.min(tokens.len());
for (index, token) in tokens.iter().enumerate().take(upper).skip(from) {
match token {
Token::OpenParen | Token::OpenBrace => depth += 1,
Token::CloseParen | Token::CloseBrace => depth -= 1,
Token::Arrow if depth == 0 => return Some(index),
_ => {}
}
}
None
}
fn pattern_start_before(tokens: &[Token], body_start: usize, next_arrow: usize) -> usize {
if next_arrow == 0 {
return body_start;
}
let mut index = next_arrow; if index > body_start && matches!(tokens.get(index - 1), Some(Token::CloseParen)) {
let mut depth = 0_i32;
let mut scan = index - 1;
loop {
match tokens.get(scan) {
Some(Token::CloseParen) => depth += 1,
Some(Token::OpenParen) => {
depth -= 1;
if depth == 0 {
index = scan;
break;
}
}
_ => {}
}
if scan == body_start {
break;
}
scan -= 1;
}
}
if index > body_start {
index -= 1;
}
index.max(body_start)
}
pub(super) fn arm_label(tokens: &[Token], arm: &Arm) -> ArmLabel {
match tokens.get(arm.pattern_start) {
Some(Token::Ident(word)) if word == "Ok" => ArmLabel::Ok,
Some(Token::Ident(word)) if word == "Error" => ArmLabel::Error,
Some(Token::Ident(word)) if word == "_" => ArmLabel::Wildcard,
Some(Token::Ident(word)) => ArmLabel::Pattern(word.clone()),
Some(Token::Qualified { left, right }) => ArmLabel::Pattern(format!("{left}.{right}")),
_ => ArmLabel::Wildcard,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::structure::scan::tokenise;
fn arms_of(body: &str) -> (Vec<Token>, Vec<Arm>) {
let src = format!("case x {{ {body} }}");
let tokens = tokenise(&src);
let open = tokens
.iter()
.position(|t| matches!(t, Token::OpenBrace))
.unwrap_or_else(|| unreachable!("wrapped source has an opening brace"));
let close = tokens
.iter()
.rposition(|t| matches!(t, Token::CloseBrace))
.unwrap_or_else(|| unreachable!("wrapped source has a closing brace"));
let arms = split_arms(&tokens, open + 1, close);
(tokens, arms)
}
#[test]
fn two_expression_arms_split_cleanly() {
let (tokens, arms) = arms_of("Ok(a) -> charge(a) Error(e) -> done(e)");
assert_eq!(arms.len(), 2);
assert_eq!(arm_label(&tokens, &arms[0]), ArmLabel::Ok);
assert_eq!(arm_label(&tokens, &arms[1]), ArmLabel::Error);
let body0: Vec<_> = tokens[arms[0].body_start..arms[0].body_end].to_vec();
assert!(!body0.contains(&Token::Ident("Error".to_owned())));
assert!(body0.contains(&Token::Ident("charge".to_owned())));
}
#[test]
fn block_body_arm_is_bounded_by_braces() {
let (tokens, arms) = arms_of("Ok(a) -> ok(a) Error(e) -> { comp(e) more(e) }");
assert_eq!(arms.len(), 2);
let body1: Vec<_> = tokens[arms[1].body_start..arms[1].body_end].to_vec();
assert!(body1.contains(&Token::Ident("comp".to_owned())));
assert!(body1.contains(&Token::Ident("more".to_owned())));
}
#[test]
fn wildcard_arm_is_labelled() {
let (tokens, arms) = arms_of("_ -> fallback()");
assert_eq!(arms.len(), 1);
assert_eq!(arm_label(&tokens, &arms[0]), ArmLabel::Wildcard);
}
}