use crate::lexer::{Token, TokenKind, Span};
pub struct TokenRewriter {
tokens: Vec<Token>,
position: usize,
output: Vec<Token>,
fixes_applied: usize,
}
impl TokenRewriter {
pub fn new(tokens: Vec<Token>) -> Self {
Self {
tokens,
position: 0,
output: Vec::new(),
fixes_applied: 0,
}
}
pub fn rewrite(mut self) -> (Vec<Token>, usize) {
while self.position < self.tokens.len() {
if self.try_fix_if_let_path_qualified() {
continue;
}
self.output.push(self.tokens[self.position].clone());
self.position += 1;
}
(self.output, self.fixes_applied)
}
fn try_fix_if_let_path_qualified(&mut self) -> bool {
if !self.matches_sequence(&[
|t| matches!(t, TokenKind::If),
|t| matches!(t, TokenKind::Let),
]) {
return false;
}
let start_pos = self.position;
let mut lookahead = self.position + 2; let mut has_path_qualifier = false;
while lookahead < self.tokens.len() {
match &self.tokens[lookahead].kind {
TokenKind::Eq => break, TokenKind::ColonColon => {
has_path_qualifier = true;
break;
}
_ => lookahead += 1,
}
}
if !has_path_qualifier {
return false; }
self.position += 2;
let mut pattern_tokens = Vec::new();
while self.position < self.tokens.len() {
if matches!(self.tokens[self.position].kind, TokenKind::Eq) {
self.position += 1; break;
}
pattern_tokens.push(self.tokens[self.position].clone());
self.position += 1;
}
let mut expr_tokens = Vec::new();
while self.position < self.tokens.len() {
if matches!(self.tokens[self.position].kind, TokenKind::LBrace) {
break;
}
expr_tokens.push(self.tokens[self.position].clone());
self.position += 1;
}
let mut body_tokens = Vec::new();
if self.position < self.tokens.len() &&
matches!(self.tokens[self.position].kind, TokenKind::LBrace) {
let start_span = self.tokens[self.position].span;
body_tokens.push(self.tokens[self.position].clone());
self.position += 1;
let mut brace_depth = 1;
while self.position < self.tokens.len() && brace_depth > 0 {
match self.tokens[self.position].kind {
TokenKind::LBrace => brace_depth += 1,
TokenKind::RBrace => brace_depth -= 1,
_ => {}
}
body_tokens.push(self.tokens[self.position].clone());
self.position += 1;
}
}
let has_else = self.position < self.tokens.len() &&
matches!(self.tokens[self.position].kind, TokenKind::Else);
let mut else_body_tokens = Vec::new();
if has_else {
self.position += 1;
if self.position < self.tokens.len() &&
matches!(self.tokens[self.position].kind, TokenKind::LBrace) {
else_body_tokens.push(self.tokens[self.position].clone()); self.position += 1;
let mut brace_depth = 1;
while self.position < self.tokens.len() && brace_depth > 0 {
match self.tokens[self.position].kind {
TokenKind::LBrace => brace_depth += 1,
TokenKind::RBrace => brace_depth -= 1,
_ => {}
}
else_body_tokens.push(self.tokens[self.position].clone());
self.position += 1;
}
}
}
let first_span = self.tokens[start_pos].span;
self.output.push(Token::new(TokenKind::Match, first_span));
self.output.extend(expr_tokens);
self.output.push(Token::new(TokenKind::LBrace, first_span));
self.output.extend(pattern_tokens);
self.output.push(Token::new(TokenKind::DDArrow, first_span));
self.output.extend(body_tokens);
self.output.push(Token::new(TokenKind::Comma, first_span));
self.output.push(Token::new(TokenKind::Ident("_".to_string()), first_span));
self.output.push(Token::new(TokenKind::DDArrow, first_span));
if else_body_tokens.is_empty() {
self.output.push(Token::new(TokenKind::LBrace, first_span));
self.output.push(Token::new(TokenKind::RBrace, first_span));
} else {
self.output.extend(else_body_tokens);
}
self.output.push(Token::new(TokenKind::RBrace, first_span));
self.fixes_applied += 1;
true
}
fn matches_sequence(&self, predicates: &[fn(&TokenKind) -> bool]) -> bool {
if self.position + predicates.len() > self.tokens.len() {
return false;
}
for (i, predicate) in predicates.iter().enumerate() {
if !predicate(&self.tokens[self.position + i].kind) {
return false;
}
}
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_if_let_no_fix() {
let tokens = vec![
Token::new(TokenKind::If, Span::new(0, 2, 1, 1)),
Token::new(TokenKind::Let, Span::new(3, 6, 1, 4)),
Token::new(TokenKind::Ident("Some".to_string()), Span::new(7, 11, 1, 8)),
Token::new(TokenKind::LParen, Span::new(11, 12, 1, 12)),
Token::new(TokenKind::Ident("x".to_string()), Span::new(12, 13, 1, 13)),
Token::new(TokenKind::RParen, Span::new(13, 14, 1, 14)),
Token::new(TokenKind::Eq, Span::new(15, 16, 1, 16)),
Token::new(TokenKind::Ident("opt".to_string()), Span::new(17, 20, 1, 18)),
Token::new(TokenKind::LBrace, Span::new(21, 22, 1, 22)),
Token::new(TokenKind::RBrace, Span::new(22, 23, 1, 23)),
];
let rewriter = TokenRewriter::new(tokens.clone());
let (result, fixes) = rewriter.rewrite();
assert_eq!(fixes, 0);
assert_eq!(result.len(), tokens.len());
}
#[test]
fn test_path_qualified_if_let_fix() {
let tokens = vec![
Token::new(TokenKind::If, Span::new(0, 2, 1, 1)),
Token::new(TokenKind::Let, Span::new(3, 6, 1, 4)),
Token::new(TokenKind::Ident("Foo".to_string()), Span::new(7, 10, 1, 8)),
Token::new(TokenKind::ColonColon, Span::new(10, 12, 1, 11)),
Token::new(TokenKind::Ident("Bar".to_string()), Span::new(12, 15, 1, 13)),
Token::new(TokenKind::LParen, Span::new(15, 16, 1, 16)),
Token::new(TokenKind::Ident("x".to_string()), Span::new(16, 17, 1, 17)),
Token::new(TokenKind::RParen, Span::new(17, 18, 1, 18)),
Token::new(TokenKind::Eq, Span::new(19, 20, 1, 20)),
Token::new(TokenKind::Ident("expr".to_string()), Span::new(21, 25, 1, 22)),
Token::new(TokenKind::LBrace, Span::new(26, 27, 1, 27)),
Token::new(TokenKind::Ident("body".to_string()), Span::new(28, 32, 1, 29)),
Token::new(TokenKind::RBrace, Span::new(33, 34, 1, 34)),
];
let rewriter = TokenRewriter::new(tokens);
let (result, fixes) = rewriter.rewrite();
assert_eq!(fixes, 1);
assert!(matches!(result[0].kind, TokenKind::Match));
assert!(matches!(result[1].kind, TokenKind::Ident(ref s) if s == "expr"));
assert!(matches!(result[2].kind, TokenKind::LBrace));
assert!(matches!(result[3].kind, TokenKind::Ident(ref s) if s == "Foo"));
assert!(matches!(result[4].kind, TokenKind::ColonColon));
assert!(matches!(result[5].kind, TokenKind::Ident(ref s) if s == "Bar"));
}
}