use crate::phenotype::{Event, PhenotypeBuilder};
pub trait Instruction {
type Value: Default;
type Input;
fn execute(&self, stack: &mut Vec<Self::Value>, input: &Self::Input);
}
#[derive(Debug)]
pub struct Bytecode<T>(Vec<T>);
impl<T> Bytecode<T> {
pub fn new(instructions: Vec<T>) -> Self {
Self(instructions)
}
pub fn instructions(&self) -> &[T] {
&self.0
}
}
impl<T: Instruction> Bytecode<T> {
pub fn run(&self, input: &T::Input) -> T::Value {
let mut stack: vecpool::PoolVec<T::Value> = vecpool::PoolVec::new();
for instruction in &self.0 {
instruction.execute(&mut *stack, input);
}
stack.pop().unwrap_or_default()
}
}
#[derive(Debug)]
pub struct BytecodeBuilder<T>(vecpool::PoolVec<T>);
impl<T> Default for BytecodeBuilder<T> {
fn default() -> Self {
Self(vecpool::PoolVec::new())
}
}
impl<T: Instruction> PhenotypeBuilder<T> for BytecodeBuilder<T> {
type Output = Bytecode<T>;
fn push(&mut self, event: Event<T>) {
if let Event::Terminal(t) = event {
self.0.push(t);
}
}
fn finish(self) -> Bytecode<T> {
Bytecode(self.0.into_vec())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone)]
enum Op {
Push(f64),
Add,
InputX,
}
impl Instruction for Op {
type Value = f64;
type Input = f64;
fn execute(&self, stack: &mut Vec<f64>, input: &f64) {
match self {
Op::Push(v) => stack.push(*v),
Op::Add => {
let b = stack.pop().unwrap_or_default();
let a = stack.pop().unwrap_or_default();
stack.push(a + b);
}
Op::InputX => stack.push(*input),
}
}
}
#[test]
fn bytecode_runs_stack_machine() {
let prog = Bytecode::new(vec![Op::InputX, Op::Push(3.0), Op::Add]);
assert_eq!(prog.run(&2.0), 5.0);
}
#[test]
fn builder_collects_terminals() {
let mut builder = BytecodeBuilder::default();
builder.push(Event::BeginRule);
builder.push(Event::Terminal(Op::Push(1.0)));
builder.push(Event::Terminal(Op::Push(2.0)));
builder.push(Event::Terminal(Op::Add));
builder.push(Event::EndRule);
let prog = builder.finish();
assert_eq!(prog.run(&0.0), 3.0);
}
#[test]
fn empty_program_returns_default() {
let prog: Bytecode<Op> = Bytecode::new(vec![]);
assert_eq!(prog.run(&1.0), 0.0);
}
}