use std::fmt;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use rand::Rng;
use rand_distr::{Distribution, Standard};
use strum::EnumCount as EnumCountTrait;
use strum_macros::{EnumCount, EnumIter};
use crate::ops::mutation::mutate_creep;
#[derive(
Debug, Copy, Clone, PartialEq, PartialOrd, IntoPrimitive, TryFromPrimitive, EnumCount, EnumIter,
)]
#[repr(u8)]
pub enum Opcode {
Nop = 0, Add = 1, Sub = 2, Mul = 3, Div = 4, Abs = 5, Neg = 6, Pow = 7, Log = 8, Load = 9, IndirectCopy = 10, Jlt = 11, Jle = 12, Jeq = 13, }
impl Opcode {
#[must_use]
pub fn operand(&self, idx: usize) -> Operand {
match self {
Opcode::Nop => Operand::None,
Opcode::Add
| Opcode::Sub
| Opcode::Mul
| Opcode::Div
| Opcode::IndirectCopy
| Opcode::Pow => {
if idx <= 1 {
Operand::Register
} else {
Operand::None
}
}
Opcode::Abs | Opcode::Neg | Opcode::Log => {
if idx == 0 {
Operand::Register
} else {
Operand::None
}
}
Opcode::Load => {
if idx == 0 {
Operand::Register
} else {
Operand::Immediate
}
}
Opcode::Jlt | Opcode::Jle | Opcode::Jeq => {
if idx == 2 {
Operand::Relative
} else {
Operand::Register
}
}
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub enum Operand {
None,
Register,
Immediate,
Relative, }
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub struct Op {
pub op: Opcode,
pub data: [u8; 3], }
impl fmt::Display for Op {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let rx = self.data[0];
let ry = self.data[1];
match self.op {
Opcode::Nop => f.write_fmt(format_args!("nop")),
Opcode::Add => f.write_fmt(format_args!("add r{}, r{}", rx, ry)),
Opcode::Sub => f.write_fmt(format_args!("sub r{}, r{}", rx, ry)),
Opcode::Mul => f.write_fmt(format_args!("mul r{}, r{}", rx, ry)),
Opcode::Div => f.write_fmt(format_args!("div r{}, r{}", rx, ry)),
Opcode::Abs => f.write_fmt(format_args!("abs r{}", rx)),
Opcode::Neg => f.write_fmt(format_args!("neg r{}", rx)),
Opcode::Pow => f.write_fmt(format_args!("pow r{}, r{}", rx, ry)),
Opcode::Log => f.write_fmt(format_args!("ln r{}", rx)),
Opcode::Load => {
let lo = self.data[1];
let hi = self.data[2];
f.write_fmt(format_args!("load r{}, {}", rx, (hi as f64) + (lo as f64) / 256.0))
}
Opcode::IndirectCopy => f.write_fmt(format_args!("mov [r{}], r{}", rx, ry)),
Opcode::Jlt => {
let imm = self.data[2] as i8;
f.write_fmt(format_args!("jlt r{}, r{}, {}", rx, ry, imm))
}
Opcode::Jle => {
let imm = self.data[2] as i8;
f.write_fmt(format_args!("jle r{}, r{}, {}", rx, ry, imm))
}
Opcode::Jeq => {
let imm = self.data[2] as i8;
f.write_fmt(format_args!("jeq r{}, r{}, {}", rx, ry, imm))
}
}
}
}
impl Distribution<Opcode> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Opcode {
Opcode::try_from_primitive(rng.gen_range(0..Opcode::COUNT) as u8).unwrap()
}
}
impl Op {
#[must_use]
pub fn new(op: Opcode, data: [u8; 3]) -> Self {
Self { op, data }
}
#[must_use]
pub fn rand(op: Opcode, num_reg: usize, code_size: usize) -> Self {
let mut r = rand::thread_rng();
let mut data = [0, 0, 0];
let code_size = code_size.clamp(0, i8::MAX as usize) as i32;
for (i, v) in data.iter_mut().enumerate() {
match op.operand(i) {
Operand::None => {}
Operand::Register => *v = r.gen_range(0..num_reg) as u8,
Operand::Immediate => *v = r.gen::<u8>(),
Operand::Relative => *v = r.gen_range(-code_size..=code_size) as u8,
}
}
Op::new(op, data)
}
#[must_use]
pub fn num_operands(&self) -> usize {
for i in 0..self.data.len() {
if self.op.operand(i) == Operand::None {
return i;
}
}
self.data.len()
}
pub fn mutate(&mut self, num_reg: usize, code_size: usize) {
let mut r = rand::thread_rng();
let num_operands = self.num_operands();
if num_operands == 0 {
return;
}
let idx = r.gen_range(0..num_operands);
let code_size = code_size.clamp(0, i8::MAX as usize) as i8;
match self.op.operand(idx) {
Operand::None => {}
Operand::Register => self.data[idx] = r.gen_range(0..num_reg) as u8,
Operand::Immediate => self.data[idx] = mutate_creep(self.data[idx], 64),
Operand::Relative => {
self.data[idx] = mutate_creep(self.data[idx] as i8, code_size)
.clamp(-code_size, code_size) as u8;
}
}
}
#[must_use]
pub fn dist(a: &Op, b: &Op) -> f64 {
let mut d = 0.0;
if a.op != b.op {
d += 100.0; }
for (&a, &b) in a.data.iter().zip(b.data.iter()) {
d += (a as f64 - b as f64).abs();
}
d
}
}