#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::{
vec::Vec,
};
use core::fmt::Display;
use core::iter::{self, Empty};
use tiny_vec::{Cow, TinyVec};
const DEFAULT_MEM_SIZE: usize = tiny_vec::n_elements_for_stack::<u8>();
const DEFAULT_PROGRAM_SIZE: usize = tiny_vec::n_elements_for_stack::<u8>();
const DEFAULT_LOOP_STACK_SIZE: usize = tiny_vec::n_elements_for_stack::<usize>();
#[derive(Clone, Debug, Default)]
pub struct Interpreter<'prog,
In,
Out,
const MS: usize = DEFAULT_MEM_SIZE,
const PS: usize = DEFAULT_PROGRAM_SIZE,
const LS: usize = DEFAULT_LOOP_STACK_SIZE,
>
where
In: Iterator<Item = u8>,
Out: Writer,
{
memory: TinyVec<u8, MS>,
ptr: usize,
pc: usize,
program: Cow<'prog, u8, PS>,
input: In,
output: Out,
open_loops: TinyVec<usize, LS>,
skiping_open_loop: Option<usize>,
}
impl<'prog, In, Out> Interpreter<'prog, In, Out>
where
In: Iterator<Item = u8>,
Out: Writer
{
#[inline]
pub fn new<Prog>(program: Prog, input: In, output: Out) -> Self
where
Prog: Into<Cow<'prog, u8, DEFAULT_PROGRAM_SIZE>>
{
Interpreter::with_custom_stack(program, input, output)
}
}
impl<'prog, In, Out,
const MS: usize,
const PS: usize,
const LS: usize
> Interpreter<'prog, In, Out, MS, PS, LS>
where
In: Iterator<Item = u8>,
Out: Writer
{
pub fn with_custom_stack<Prog>(program: Prog, input: In, output: Out) -> Self
where
Prog: Into<Cow<'prog, u8, PS>>
{
let mut memory = TinyVec::<u8, MS>::new();
memory.resize(MS, 0);
Self {
pc: 0,
memory,
program: program.into(),
input,
output,
ptr: 0,
open_loops: TinyVec::<_, LS>::new(),
skiping_open_loop: None,
}
}
pub fn into_owned(self) -> Interpreter<'static, In, Out, MS, PS, LS> {
let Self { memory, ptr, pc, program, input, output, open_loops: loop_starts, skiping_open_loop: parsing_open_loop } = self;
Interpreter {
memory,
program: Cow::<'static, _, PS>::Owned(program.into_owned()),
ptr,
pc,
input,
output,
open_loops: loop_starts,
skiping_open_loop: parsing_open_loop
}
}
pub fn push_instruction(&mut self, ins: u8) {
self.program.to_mut().push(ins);
}
pub fn push_instruction_slice(&mut self, ins: &[u8]) {
self.program.to_mut().extend_from_slice(ins);
}
pub fn push_instructions_iter<I>(&mut self, iterator: I)
where
I: Iterator<Item = u8>
{
self.program.to_mut().extend(iterator);
}
pub fn run(&mut self) -> Result<(), Error> {
while self.pc < self.program.len() {
self.step()?;
}
if self.open_loops.is_empty() {
Ok(())
} else {
Err(Error::OpenLoopsRemain)
}
}
fn skip_loop(&mut self) -> Result<(), Error> {
let mut nest = self.skiping_open_loop.take().unwrap_or(1);
while nest != 0 {
let next_ins = self.program.get(self.pc)
.copied()
.ok_or_else(|| {
self.skiping_open_loop = Some(nest);
Error::OpenLoopsRemain
})?;
if next_ins == b'[' { nest += 1; }
if next_ins == b']' { nest -= 1; }
self.pc += 1;
}
Ok(())
}
pub fn step(&mut self) -> Result<(), Error> {
if self.skiping_open_loop.is_some() {
return self.skip_loop();
}
if self.pc >= self.program.len() {
return if self.open_loops.is_empty() {
Ok(())
} else {
Err(Error::OpenLoopsRemain)
}
}
let ins = self.program[self.pc];
self.pc += 1;
match ins {
b'>' => {
let cap = self.memory.capacity();
if self.ptr == cap {
self.memory.extend_from_slice(&[0; 100]);
}
self.ptr += 1;
},
b'<' => self.ptr -= 1,
b'+' => self.memory[self.ptr] += 1,
b'-' => self.memory[self.ptr] -= 1,
b'.' => {
let b = self.memory[self.ptr];
self.output.push_byte(b).map_err(|_| Error::Output)?;
},
b',' => {
let b = self.input.next().unwrap_or(b'\0');
self.memory[self.ptr] = b;
},
b'[' => {
if self.memory[self.ptr] == 0 {
self.skip_loop()?;
} else {
self.open_loops.push(self.pc - 1);
}
},
b']' => {
let start = self.open_loops.pop().ok_or(Error::MissingOpenLoop)?;
if self.memory[self.ptr] != 0 {
self.pc = start;
}
},
c if c.is_ascii_whitespace() => {},
_ => return Err(Error::UnexpectedByte(ins)),
}
Ok(())
}
#[inline]
pub const fn get_memory(&self) -> &[u8] { self.memory.as_slice() }
pub const fn lives_on_stack(&self) -> bool {
self.memory.lives_on_stack()
&& self.open_loops.lives_on_stack()
&& self.program.lives_on_stack()
}
}
#[cfg(feature = "alloc")]
impl<'prog, In> Interpreter<'prog, In, Vec<u8>>
where
In: Iterator<Item = u8>,
{
pub fn vec_output(program: impl Into<Cow<'prog, u8, DEFAULT_PROGRAM_SIZE>>, input: In) -> Self {
Self::new(program, input, Vec::new())
}
pub fn get_output(&self) -> &[u8] { &self.output }
}
impl<'prog, Out> Interpreter<'prog, Empty<u8>, Out>
where
Out: Writer
{
pub fn empty_input(program: impl Into<Cow<'prog, u8, DEFAULT_PROGRAM_SIZE>>, output: Out) -> Self {
Self::with_custom_stack(program, iter::empty(), output)
}
}
#[cfg(feature = "alloc")]
impl<'prog> Interpreter<'prog, Empty<u8>, Vec<u8>> {
pub fn vec_output_empty_input(program: impl Into<Cow<'prog, u8, DEFAULT_PROGRAM_SIZE>>) -> Self {
Self::new(program, iter::empty(), Vec::new())
}
}
#[derive(Debug)]
pub enum Error {
Output,
MissingOpenLoop,
UnexpectedByte(u8),
OpenLoopsRemain,
}
impl Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Error::UnexpectedByte(b) => write!(f, "Got unexpected instruction '{}' ({b})", *b as char),
Error::Output => write!(f, "Error writing to output"),
Error::MissingOpenLoop => write!(f, "Missing open '[' for ']'"),
Error::OpenLoopsRemain => write!(f, "EOF reached with loops still open"),
}
}
}
pub trait Writer {
type Error;
fn push_byte(&mut self, byte: u8) -> Result<(), Self::Error>;
}
pub struct NoOutput;
impl Writer for NoOutput {
type Error = core::convert::Infallible;
fn push_byte(&mut self, _byte: u8) -> Result<(), Self::Error> {
Ok(())
}
}
#[cfg(feature = "std")]
impl <T: std::io::Write> Writer for T {
type Error = std::io::Error;
fn push_byte(&mut self, byte: u8) -> Result<(), Self::Error> {
self.write_all(&[byte])
}
}
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
impl<const N: usize> Writer for TinyVec<u8, N> {
type Error = core::convert::Infallible;
fn push_byte(&mut self, byte: u8) -> Result<(), Self::Error> {
self.push(byte);
Ok(())
}
}
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
impl<const N: usize> Writer for &mut TinyVec<u8, N> {
type Error = core::convert::Infallible;
fn push_byte(&mut self, byte: u8) -> Result<(), Self::Error> {
self.push(byte);
Ok(())
}
}
#[cfg(all(not(feature = "std"), feature = "alloc"))]
impl Writer for Vec<u8> {
type Error = core::convert::Infallible;
fn push_byte(&mut self, byte: u8) -> Result<(), Self::Error> {
self.push(byte);
Ok(())
}
}
#[cfg(all(test, feature = "std"))]
mod test;
#[cfg(all(test, not(feature = "std")))]
mod test_no_std;