[Mark Doyle's Website]

[Advent of Code]

Day 3

use crate::*;
use std::fmt::Display;

impl Day3 for Year2024 {
    fn part1(input: String) -> impl Display {
        let mut fsm = FSM::new();
        let mut sum = 0;

        for ch in input.chars() {
            match fsm.next(ch) {
                State::End(a, b) => sum += a * b,
                _ => (),
            };
        }

        sum
    }

    fn part2(input: String) -> impl Display {
        let input = input;
        let mut toggle_fsm = ToggleFSM::new();
        let mut fsm = FSM::new();
        let mut sum = 0;

        let mut is_accepting = true;

        for ch in input.chars() {
            match toggle_fsm.next(ch) {
                Tog::Open => is_accepting = true,
                Tog::Close => is_accepting = false,
                _ => (),
            };

            match fsm.next(ch) {
                State::End(a, b) if is_accepting => sum += a * b,
                _ => (),
            }
        }

        sum
    }
}

#[derive(Debug)]
enum Tog {
    Do(char),
    Dont(char),
    Idle,
    Open,
    Close,
}

struct ToggleFSM {
    inner: Tog,
}

impl ToggleFSM {
    fn new() -> ToggleFSM {
        ToggleFSM { inner: Tog::Idle }
    }

    fn next(&mut self, ch: char) -> &Tog {
        let new = match (&self.inner, ch) {
            (Tog::Idle | Tog::Open | Tog::Close, 'd') => Tog::Do('o'),
            (Tog::Do('o'), 'o') => Tog::Do('('),
            (Tog::Do('('), '(') => Tog::Do(')'),
            (Tog::Do('('), 'n') => Tog::Dont('\''),
            (Tog::Do(')'), ')') => Tog::Open,
            (Tog::Dont('\''), '\'') => Tog::Dont('t'),
            (Tog::Dont('t'), 't') => Tog::Dont('('),
            (Tog::Dont('('), '(') => Tog::Dont(')'),
            (Tog::Dont(')'), ')') => Tog::Close,
            _ => Tog::Idle,
        };

        println!("new=[{new:?}]");
        self.inner = new;
        &self.inner
    }
}

#[derive(Debug)]
enum State {
    InMul(char),
    OpenParens(Option<String>),
    CloseParens(String, Option<String>),
    End(i32, i32),
    Idle,
}

struct FSM {
    inner: State,
}

impl FSM {
    fn new() -> FSM {
        FSM { inner: State::Idle }
    }

    #[allow(unused)]
    fn state(&self) -> &State {
        &self.inner
    }

    fn transition(&mut self, new: State) {
        println!("new=[{new:?}]");
        self.inner = new;
    }

    fn next(&mut self, ch: char) -> &State {
        let new = match &self.inner {
            State::Idle | State::End(_, _) => {
                if ch == 'm' {
                    State::InMul('u')
                } else {
                    State::Idle
                }
            }
            State::InMul(next) => {
                if ch != *next {
                    State::Idle
                } else {
                    match next {
                        'u' => State::InMul('l'),
                        'l' => State::InMul('('),
                        '(' => State::OpenParens(None),
                        _ => panic!("moved into invalid state InMul({next})"),
                    }
                }
            }
            State::OpenParens(cur) => match cur {
                Some(cur) => match ch {
                    '0'..='9' => State::OpenParens(Some(format!("{cur}{ch}"))),
                    ',' => State::CloseParens(cur.to_owned(), None),
                    _ => State::Idle,
                },
                None if ch.is_ascii_digit() => State::OpenParens(Some(ch.to_string())),
                None => State::Idle,
            },
            State::CloseParens(first, cur) => match cur {
                Some(cur) => match ch {
                    '0'..='9' => State::CloseParens(first.to_owned(), Some(format!("{cur}{ch}"))),
                    ')' => State::End(first.parse().unwrap(), cur.parse().unwrap()),
                    _ => State::Idle,
                },
                None if ch.is_ascii_digit() => {
                    State::CloseParens(first.to_owned(), Some(ch.to_string()))
                }
                None => State::Idle,
            },
        };

        self.transition(new);
        &self.inner
    }
}

#[cfg(test)]
mod test {
    use std::assert_matches::assert_matches;

    use super::*;

    #[test]
    fn fsm_returns_to_idle_on_bad_content_inside_parens() {
        let mut fsm = FSM::new();

        for ch in "mul(BAD)".chars() {
            fsm.next(ch);
        }

        assert_matches!(fsm.state(), State::Idle)
    }

    #[test]
    fn fsm_parses_first_digit_inside_parens() {
        let mut fsm = FSM::new();

        for ch in "mul(1".chars() {
            fsm.next(ch);
        }

        assert_matches!(fsm.state(), State::OpenParens(Some(_)))
    }

    #[test]
    fn fsm_parses_multi_digit_num_in_first_pos() {
        let mut fsm = FSM::new();

        for ch in "mul(100".chars() {
            fsm.next(ch);
        }

        match fsm.state() {
            State::OpenParens(Some(v)) => assert_eq!("100", v),
            _ => panic!("infallible"),
        }
    }

    #[test]
    fn fsm_moves_into_closed_paren_state() {
        let mut fsm = FSM::new();

        for ch in "mul(100,".chars() {
            fsm.next(ch);
        }

        assert_matches!(fsm.state(), State::CloseParens(_, None))
    }

    #[test]
    fn fsm_parses_num_in_second_position() {
        let mut fsm = FSM::new();

        for ch in "mul(100,20".chars() {
            fsm.next(ch);
        }

        match fsm.state() {
            State::CloseParens(_, Some(v)) => assert_eq!("20", v),
            _ => panic!("infallible"),
        }
    }

    #[test]
    fn fsm_moves_to_end_state() {
        let mut fsm = FSM::new();

        for ch in "mul(32,64)".chars() {
            fsm.next(ch);
        }

        assert_matches!(fsm.state(), State::End(32, 64))
    }

    #[test]
    fn toggles_fsm_matches_do() {
        let mut fsm = ToggleFSM::new();

        for ch in "do()".chars() {
            fsm.next(ch);
        }

        assert_matches!(fsm.inner, Tog::Open)
    }

    #[test]
    fn toggles_fsm_matches_dont() {
        let mut fsm = ToggleFSM::new();

        for ch in "don't()".chars() {
            fsm.next(ch);
        }

        assert_matches!(fsm.inner, Tog::Close)
    }
}
*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|*/_\/_*_\/__*__\/___*___\|