sbrain 0.99.0

A library for evaluating Semantic Brain, a minimalistic multiparadigm programming language optimized for genetic programming applications.
Documentation
//! The implementation of the SBrain VM.
use crate::{MAddr, MData};
use std::io;
use std::io::{Read, Write};
use std::u16::MAX as u16MAX;

/// A virtual machine modelling the SBrain Turing machine.
/// This machine implements the specification relatively strictly, providing exactly 2^16 (65536)
/// data and instruction cells. Thus, all pointers are 16 bits and all data is 8 bits.
/// The main deviation from the minimum specification is the jump stack, which is indefinitely
/// expandable.

pub struct SBrainVM<'a> {
    // Data containers
    /// The data tape contains the primary data on which the program will operate
    /// 16-bit addresses with a single dead address
    data_tape: [MData; 65536],
    /// The data stack allows the position-independent storage of data
    data_stack: Vec<MData>,
    /// Auxiliary register (auxi_r)
    auxi_r: MData,

    // Machine Internals
    /// The instruction tape contains instructions. This VM uses the recommended 6-bit binary
    /// format, but Rust does not have a 6-bit datatype, so u8 is used instead
    exec_tape: [u8; 65536],
    /// Pointer to the current data cell
    data_p: MAddr,
    /// Pointer to the current instruction
    inst_p: MAddr,

    // I/O Tapes
    input_t: Option<&'a mut dyn Read>,
    output_t: Option<&'a mut dyn Write>,
}

impl<'a> SBrainVM<'a> {
    /// Return a new SBrainVM, with no data in any tapes.
    /// If given a `None` `input`, all reads read 0.
    /// If given a `None` `output`, all writes are discarded.
    pub fn new(
        input: Option<&'a mut dyn Read>,
        output: Option<&'a mut dyn Write>,
        program: &[u8],
    ) -> Result<SBrainVM<'a>, String> {
        let mut new = SBrainVM {
            data_tape: [0; 65536],
            data_stack: vec![0; 256],
            auxi_r: 0,
            exec_tape: [0; 65536],
            data_p: 0,
            inst_p: 0,

            input_t: input,
            output_t: output,
        };
        new.load_program(program)?;
        Ok(new)
    }

    /// Load a program tape: copy data from the given slice into the executable tape,
    /// starting at address zero.
    /// On error, the Err(s) return will contain a message describing the error.
    pub fn load_program(&mut self, program: &[u8]) -> Result<(), String> {
        // No program can be longer than the tape the VM stores programs on.
        if program.len() > 65536 {
            return Err(String::from("Provided program exceeds VM tape length."));
        }

        // Target is a slice of the VMs executable tape of the same size as the program
        // This is required from clone_from_slice
        self.exec_tape[0..program.len()].clone_from_slice(program);
        return Ok(());
    }

    fn get_input(&mut self) -> io::Result<MData> {
        let mut buf = [0; 1];
        if let Some(ref mut r) = self.input_t {
            r.read(&mut buf)?;
            Ok(buf[0])
        } else {
            Ok(0)
        }
    }

    fn put_output(&mut self, output: MData) -> io::Result<()> {
        match &mut self.output_t {
            &mut Some(ref mut w) => {
                w.write(&[output])?;
                Ok(())
            }
            &mut None => Ok(()),
        }
    }

    /// Execute an instruction on the current virtual machine
    /// Returns true if execution is finished and false if not
    fn do_instruction(&mut self) -> io::Result<bool> {
        match self.exec_tape[self.inst_p as usize] {
            // wrapping_add() and wrapping_sub are used in order to never overflow the bounds
            // of unsigned int types
            //
            // Decr. and incr. for data_p
            0 => {
                self.data_p = self.data_p.wrapping_sub(1);
            }
            1 => {
                self.data_p = self.data_p.wrapping_add(1);
            }
            // Decr. and incr. for *data_p
            2 => {
                self.data_tape[self.data_p as usize] =
                    self.data_tape[self.data_p as usize].wrapping_sub(1);
            }
            3 => {
                self.data_tape[self.data_p as usize] =
                    self.data_tape[self.data_p as usize].wrapping_add(1);
            }
            // Jump instructions
            4 => {
                // If *data_p is 0, skip forward to the corresponding 5
                let this_inst = self.inst_p;
                if self.data_tape[self.data_p as usize] == 0 {
                    let mut nest_level = 1;
                    while nest_level > 0 {
                        self.inst_p = self.inst_p.wrapping_add(1);
                        if self.inst_p == 0 {
                            self.inst_p = this_inst;
                            break;
                        }
                        if self.exec_tape[self.inst_p as usize] == 4 {
                            nest_level += 1;
                        } else if self.exec_tape[self.inst_p as usize] == 5 {
                            nest_level -= 1;
                        }
                    }
                }
            }
            5 => {
                // If *data_p isn't 0, skip backward to the corresponding 4
                let this_inst = self.inst_p;
                if self.data_tape[self.data_p as usize] != 0 {
                    let mut nest_level = 1;
                    while nest_level > 0 {
                        self.inst_p = self.inst_p.wrapping_sub(1);
                        if self.inst_p == u16MAX {
                            self.inst_p = this_inst;
                            break;
                        }
                        if self.exec_tape[self.inst_p as usize] == 5 {
                            nest_level += 1;
                        } else if self.exec_tape[self.inst_p as usize] == 4 {
                            nest_level -= 1;
                        }
                    }
                }
            }
            // I/O commands
            6 => {
                let temp = self.data_tape[self.data_p as usize];
                self.put_output(temp)?;
            }
            7 => {
                let temp = self.get_input()?;
                self.data_tape[self.data_p as usize] = temp;
            }
            // Stack instructions
            8 => {
                self.data_stack.push(self.data_tape[self.data_p as usize]);
            }
            9 => {
                self.data_tape[self.data_p as usize] = match self.data_stack.pop() {
                    Some(n) => n,
                    None => 0,
                };
            }
            // Aux register instructions
            10 => {
                self.auxi_r = self.data_tape[self.data_p as usize];
            }
            11 => {
                self.data_tape[self.data_p as usize] = self.auxi_r;
            }
            12 => {
                self.auxi_r = 0;
            }
            // Bitwise auxi_r instructions
            //  NOT
            13 => self.auxi_r = !self.auxi_r,
            //  AND
            14 => {
                self.auxi_r = self.data_tape[self.data_p as usize] & self.auxi_r;
            }
            15 => {
                return Ok(true);
            }
            _ => {}
        }
        return Ok(false);
    }

    fn nexti(&mut self) -> bool {
        // increment the PC
        self.inst_p = self.inst_p.wrapping_add(1);
        // if it went over, wrap it and inform the caller
        if self.inst_p as usize == self.exec_tape.len() - 1 {
            self.inst_p = 0;
            return true;
        }
        return false;
    }

    /// Run the machine, until completion (cycles = None) or for n cycles (cycles = Some(n)).
    /// Return values are number of cycles run and the return code, or None if the code simply ran
    /// out of cycles.
    pub fn run(&mut self, cycles: Option<u32>) -> io::Result<(u32, Option<u8>)> {
        let mut done_cycles = 0;

        // The main execution loop
        loop {
            // Execute the current instruction.
            if self.do_instruction()? {
                return Ok((done_cycles, Some(self.auxi_r)));
            } else {
                self.nexti();
            }

            // Increment the cycle count
            done_cycles += 1;
            if let Some(n) = cycles {
                if done_cycles >= n {
                    return Ok((done_cycles, None));
                }
            }
        }
    }
}