brainfuck interpreter
brainfuck cli rust
5

Configure Feed

Select the types of activity you want to include in your feed.

feat: added positional context to runtime error messages

- current token in run loop now gets passed into each vm instruction
function for updated errors to access

digi.rip (Jun 7, 2026, 1:42 PM EDT) d03b8237 503b8dad

+51 -25
+51 -25
src/evaluator.rs
··· 7 7 8 8 #[derive(Debug)] 9 9 pub enum RuntimeError { 10 - DataPointerOutOfBounds(usize), 11 - CellOutOfBounds(usize), 12 - InvalidToken(usize), 10 + DataPointerOutOfBounds(usize, usize), 11 + CellOutOfBounds(usize, usize), 12 + InvalidToken(u8, usize, usize), 13 13 IoError(std::io::Error), 14 14 } 15 15 16 16 impl fmt::Display for RuntimeError { 17 17 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 18 18 match self { 19 - RuntimeError::DataPointerOutOfBounds(pos) => { 20 - write!(f, "Data pointer out of bounds at index {:?}", pos) 19 + RuntimeError::DataPointerOutOfBounds(line, col) => { 20 + write!( 21 + f, 22 + "Data pointer out of bounds at line {:?}, col {:?}", 23 + line, col 24 + ) 21 25 } 22 - RuntimeError::CellOutOfBounds(pos) => { 23 - write!(f, "Cell value out of bounds at index {:?}", pos) 26 + RuntimeError::CellOutOfBounds(line, col) => { 27 + write!( 28 + f, 29 + "Cell value out of bounds at line {:?}, col {:?}", 30 + line, col 31 + ) 24 32 } 25 - RuntimeError::InvalidToken(token) => { 26 - write!(f, "Invalid token at {}, check tokenizer!", token) 33 + RuntimeError::InvalidToken(line, col, token) => { 34 + write!( 35 + f, 36 + "Invalid token {:?} at at line {:?}, col {:?}, check tokenizer!", 37 + token, line, col 38 + ) 27 39 } 28 40 RuntimeError::IoError(err) => { 29 41 write!(f, "{}", err) ··· 136 148 VMBuilder::new() 137 149 } 138 150 139 - fn move_right(&mut self) -> Result<(), RuntimeError> { 151 + fn move_right(&mut self, current: &Token) -> Result<(), RuntimeError> { 140 152 if self.data_pointer + 1 >= self.tape.len() { 141 153 if self.data_oob == OOBBehavior::Error { 142 - return Err(RuntimeError::DataPointerOutOfBounds(self.inst_pointer)); 154 + return Err(RuntimeError::DataPointerOutOfBounds( 155 + current.line, 156 + current.col, 157 + )); 143 158 } else if self.data_oob == OOBBehavior::Wrap { 144 159 self.data_pointer = 0; 145 160 } ··· 150 165 Ok(()) 151 166 } 152 167 153 - fn move_left(&mut self) -> Result<(), RuntimeError> { 168 + fn move_left(&mut self, current: &Token) -> Result<(), RuntimeError> { 154 169 if self.data_pointer == 0 { 155 170 if self.data_oob == OOBBehavior::Error { 156 - return Err(RuntimeError::DataPointerOutOfBounds(self.inst_pointer)); 171 + return Err(RuntimeError::DataPointerOutOfBounds( 172 + current.line, 173 + current.col, 174 + )); 157 175 } else if self.data_oob == OOBBehavior::Wrap { 158 176 self.data_pointer = self.tape.len() - 1; 159 177 } ··· 164 182 Ok(()) 165 183 } 166 184 167 - fn increment_cell(&mut self) -> Result<(), RuntimeError> { 185 + fn increment_cell(&mut self, current: &Token) -> Result<(), RuntimeError> { 168 186 if self.cell_oob == OOBBehavior::Error { 169 187 match self.tape[self.data_pointer].checked_add(1) { 170 188 Some(val) => { 171 189 self.tape[self.data_pointer] = val; 172 190 } 173 191 None => { 174 - return Err(RuntimeError::CellOutOfBounds(self.inst_pointer)); 192 + return Err(RuntimeError::CellOutOfBounds(current.line, current.col)); 175 193 } 176 194 } 177 195 } else if self.cell_oob == OOBBehavior::Wrap { ··· 181 199 Ok(()) 182 200 } 183 201 184 - fn decrement_cell(&mut self) -> Result<(), RuntimeError> { 202 + fn decrement_cell(&mut self, current: &Token) -> Result<(), RuntimeError> { 185 203 if self.cell_oob == OOBBehavior::Error { 186 204 match self.tape[self.data_pointer].checked_sub(1) { 187 205 Some(val) => { 188 206 self.tape[self.data_pointer] = val; 189 207 } 190 208 None => { 191 - return Err(RuntimeError::CellOutOfBounds(self.inst_pointer)); 209 + return Err(RuntimeError::CellOutOfBounds(current.line, current.col)); 192 210 } 193 211 } 194 212 } else if self.cell_oob == OOBBehavior::Wrap { ··· 229 247 Ok(()) 230 248 } 231 249 232 - fn read_raw<R: std::io::BufRead>(&mut self, input: &mut R) -> Result<(), RuntimeError> { 250 + fn read_raw<R: std::io::BufRead>( 251 + &mut self, 252 + input: &mut R, 253 + current: &Token, 254 + ) -> Result<(), RuntimeError> { 233 255 let mut buffer: String = String::new(); 234 256 let mut single = [0u8; 1]; 235 257 ··· 260 282 261 283 match buffer.parse::<u16>() { 262 284 Ok(val) if val > 255 && self.cell_oob == OOBBehavior::Error => { 263 - return Err(RuntimeError::CellOutOfBounds(self.inst_pointer)); 285 + return Err(RuntimeError::CellOutOfBounds(current.line, current.col)); 264 286 } 265 287 Ok(val) => self.tape[self.data_pointer] = val as u8, 266 288 Err(_) => self.tape[self.data_pointer] = 0, ··· 300 322 301 323 match current.byte { 302 324 b'>' => { 303 - self.move_right()?; 325 + self.move_right(&current)?; 304 326 } 305 327 b'<' => { 306 - self.move_left()?; 328 + self.move_left(&current)?; 307 329 } 308 330 b'+' => { 309 - self.increment_cell()?; 331 + self.increment_cell(&current)?; 310 332 } 311 333 b'-' => { 312 - self.decrement_cell()?; 334 + self.decrement_cell(&current)?; 313 335 } 314 336 315 337 b',' => { ··· 322 344 if self.input_type == IOFormat::Ascii { 323 345 self.read_ascii(&mut reader)?; 324 346 } else if self.input_type == IOFormat::Raw { 325 - self.read_raw(&mut reader)?; 347 + self.read_raw(&mut reader, &current)?; 326 348 } 327 349 } 328 350 ··· 350 372 } 351 373 } 352 374 353 - _ => Err(RuntimeError::InvalidToken(self.inst_pointer))?, 375 + _ => Err(RuntimeError::InvalidToken( 376 + current.byte, 377 + current.line, 378 + current.col, 379 + ))?, 354 380 } 355 381 356 382 if self.tracing {