use std::iter::Peekable;
#[derive(Debug, PartialEq)]
pub enum Token {
Cross,
Num(usize),
Position(char),
}
pub fn tokenize(input: &str) -> Result<Vec<Token>, String> {
let mut tokens = Vec::<Token>::new();
let mut it = input.chars().peekable();
while let Some(&c) = it.peek() {
match c {
'x' => {
tokens.push(Token::Cross);
it.next();
}
'l' | 'c' | 'r' => {
tokens.push(Token::Position(c));
it.next();
}
'1'...'9' => {
it.next();
let n = get_num(c, &mut it);
tokens.push(Token::Num(n));
}
' ' => {
it.next();
}
'0' => return Err("Column/row number must be > 0".to_string()),
_ => return Err(format!("Unexpected input '{}'", c)),
}
}
Ok(tokens)
}
fn get_num<T: Iterator<Item = char>>(c: char, iter: &mut Peekable<T>) -> usize {
let mut n = c.to_digit(10).unwrap() as usize;
while let Some(Ok(digit)) = iter.peek().map(|c| c.to_string().parse::<usize>()) {
n = n * 10 + digit;
iter.next();
}
n
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_spec() {
let input = "3 x 5";
assert_eq!(
tokenize(&input),
Ok(vec![Token::Num(3), Token::Cross, Token::Num(5)])
);
}
#[test]
fn test_complex_spec() {
let input = "6lcr x 2";
assert_eq!(
tokenize(&input),
Ok(vec![
Token::Num(6),
Token::Position('l'),
Token::Position('c'),
Token::Position('r'),
Token::Cross,
Token::Num(2)
])
);
}
#[test]
fn test_invalid_spec() {
let input = "3a x 5";
assert!(tokenize(&input).is_err());
}
}