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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum Command {
29 IncrementCell,
31 DecrementCell,
33 MoveRight,
35 MoveLeft,
37 JumpForward,
39 JumpBackward,
41 OutputFromCell,
43 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}