hohoho/
lib.rs

1use std::fmt;
2use std::str::Chars;
3
4use thiserror::Error as ThisError;
5
6#[non_exhaustive]
7#[allow(non_snake_case)]
8#[derive(ThisError, Debug)]
9pub enum Error {
10    InvalidTriplet(String),
11    MissingHoHoHO(usize),
12    MissingHOHoHo,
13}
14
15impl fmt::Display for Error {
16    #[rustfmt::skip]
17    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18        match *self {
19            Error::InvalidTriplet(ref s) => write!(f, "Invalid HOHOHO-triplet: {}", s),
20            Error::MissingHoHoHO(n)      => write!(f, "{} missing HoHoHO(s) (\"closing bracket\")", n),
21            Error::MissingHOHoHo         => write!(f, "Missing HOHoHo (\"opening bracket\")"),
22        }
23    }
24}
25
26/// An executable **HOHOHO!** command.
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum Command {
29    /// Increment memory cell at pointer.
30    IncrementCell,
31    /// Decrement memory cell at pointer.
32    DecrementCell,
33    /// Move pointer to the right (or "forward" on the "tape").
34    MoveRight,
35    /// Move pointer to the left (or "backward" on the "tape").
36    MoveLeft,
37    /// Jump past the matching `HoHoHO` if the cell at the pointer is `0`.
38    JumpForward,
39    /// Jump back to the matching `HOHoHo` if the cell at the pointer is *not* `0`.
40    JumpBackward,
41    /// Output the character signified by the memory cell where the pointer is.
42    OutputFromCell,
43    /// Input a character and store it in the memory cell where the pointer is.
44    InputToCell,
45}
46
47impl fmt::Display for Command {
48    #[rustfmt::skip]
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        match *self {
51            Command::IncrementCell  => write!(f, "HOHOHO"),
52            Command::DecrementCell  => write!(f, "HoHoHo"),
53            Command::MoveRight      => write!(f, "HOHOHo"),
54            Command::MoveLeft       => write!(f, "HoHOHO"),
55            Command::JumpForward    => write!(f, "HOHoHo"),
56            Command::JumpBackward   => write!(f, "HoHoHO"),
57            Command::OutputFromCell => write!(f, "HoHOHo"),
58            Command::InputToCell    => write!(f, "HOHoHO"),
59        }
60    }
61}
62
63impl Command {
64    fn from_str(s: &str) -> Option<Self> {
65        match s {
66            "HOHOHO" => Some(Command::IncrementCell),
67            "HoHoHo" => Some(Command::DecrementCell),
68            "HOHOHo" => Some(Command::MoveRight),
69            "HoHOHO" => Some(Command::MoveLeft),
70            "HOHoHo" => Some(Command::JumpForward),
71            "HoHoHO" => Some(Command::JumpBackward),
72            "HoHOHo" => Some(Command::OutputFromCell),
73            "HOHoHO" => Some(Command::InputToCell),
74            _ => None,
75        }
76    }
77
78    #[rustfmt::skip]
79    fn as_brainfuck(&self) -> &'static char {
80        match *self {
81            Command::IncrementCell  => &'+',
82            Command::DecrementCell  => &'-',
83            Command::MoveRight      => &'>',
84            Command::MoveLeft       => &'<',
85            Command::JumpForward    => &'[',
86            Command::JumpBackward   => &']',
87            Command::OutputFromCell => &'.',
88            Command::InputToCell    => &',',
89        }
90    }
91}
92
93pub struct Program {
94    commands: Vec<Command>,
95}
96
97struct TripletParseIter<'a> {
98    source: Chars<'a>,
99    command_buf: String,
100    jump_stack: Vec<()>,
101    done: bool,
102}
103
104impl<'a> TripletParseIter<'a> {
105    pub fn new(source: &'a str) -> Self {
106        Self {
107            source: source.chars(),
108            command_buf: String::new(),
109            jump_stack: Vec::new(),
110            done: false,
111        }
112    }
113
114    fn try_match_triplet(&mut self) -> Result<Command, Error> {
115        if let Some(cmd) = Command::from_str(self.command_buf.as_ref()) {
116            match cmd {
117                Command::JumpForward => {
118                    self.jump_stack.push(());
119                }
120                Command::JumpBackward => {
121                    if self.jump_stack.pop().is_none() {
122                        return Err(Error::MissingHOHoHo);
123                    }
124                }
125                _ => {}
126            };
127
128            Ok(cmd)
129        } else {
130            Err(Error::InvalidTriplet(self.command_buf.clone()))
131        }
132    }
133}
134
135impl<'a> Iterator for TripletParseIter<'a> {
136    type Item = Result<Command, Error>;
137
138    fn next(&mut self) -> Option<Self::Item> {
139        if self.done {
140            return None;
141        }
142
143        self.command_buf.clear();
144
145        for c in self.source.by_ref() {
146            if c.is_whitespace() {
147                continue;
148            }
149
150            self.command_buf.push(c);
151
152            if self.command_buf.len() == 6 {
153                let res = self.try_match_triplet();
154
155                if res.is_err() {
156                    self.done = true;
157                }
158
159                return Some(res);
160            }
161        }
162
163        if !self.jump_stack.is_empty() {
164            let missing_count = self.jump_stack.len();
165            return Some(Err(Error::MissingHoHoHO(missing_count)));
166        }
167
168        self.done = true;
169
170        None
171    }
172}
173
174impl Program {
175    pub fn parse(source: &str) -> Result<Self, Error> {
176        let commands = TripletParseIter::new(source).collect::<Result<Vec<Command>, Error>>()?;
177
178        Ok(Program { commands })
179    }
180
181    pub fn to_brainfuck(self) -> Result<brainfuck::program::Program, brainfuck::program::Error> {
182        let bf: String = String::from_iter(self.commands.into_iter().map(|cmd| cmd.as_brainfuck()));
183        brainfuck::program::Program::parse(&bf)
184    }
185}