#[cfg(not(target_arch = "wasm32"))]
use std::time::{SystemTime, UNIX_EPOCH};
use crate::types::{Entry, Instruction};
pub struct SimpleRng {
state: u64,
}
impl SimpleRng {
pub fn new(seed: u64) -> Self {
SimpleRng {
state: seed.wrapping_add(1),
}
}
pub fn next_u64(&mut self) -> u64 {
self.state = self
.state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
self.state
}
pub fn next_usize(&mut self, max: usize) -> usize {
if max == 0 {
return 0;
}
(self.next_u64() as usize) % max
}
pub fn next_range(&mut self, min: i64, max: i64) -> i64 {
if min >= max {
return min;
}
let range = (max - min) as u64;
min + (self.next_u64() % range) as i64
}
}
#[derive(Clone, Debug)]
pub enum MutationStrategy {
ConstantTweak,
WordSwap,
InstructionDelete,
InstructionDup,
}
impl MutationStrategy {
pub fn label(&self) -> &str {
match self {
MutationStrategy::ConstantTweak => "constant-tweak",
MutationStrategy::WordSwap => "word-swap",
MutationStrategy::InstructionDelete => "instruction-delete",
MutationStrategy::InstructionDup => "instruction-dup",
}
}
}
#[derive(Clone, Debug)]
pub struct MutationRecord {
pub word_name: String,
pub word_index: usize,
pub strategy: MutationStrategy,
pub original_body: Vec<Instruction>,
pub description: String,
pub timestamp: u64,
}
impl MutationRecord {
pub fn format(&self) -> String {
format!(
" {} [{}]: {}",
self.word_name,
self.strategy.label(),
self.description
)
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum MutationClass {
Neutral, Beneficial, Harmful, Lethal, }
impl MutationClass {
pub fn label(&self) -> &str {
match self {
MutationClass::Neutral => "neutral",
MutationClass::Beneficial => "beneficial",
MutationClass::Harmful => "harmful",
MutationClass::Lethal => "lethal",
}
}
}
#[derive(Clone, Debug, Default)]
pub struct MutationStats {
pub total: u32,
pub neutral: u32,
pub beneficial: u32,
pub harmful: u32,
pub lethal: u32,
}
impl MutationStats {
pub fn record(&mut self, class: &MutationClass) {
self.total += 1;
match class {
MutationClass::Neutral => self.neutral += 1,
MutationClass::Beneficial => self.beneficial += 1,
MutationClass::Harmful => self.harmful += 1,
MutationClass::Lethal => self.lethal += 1,
}
}
pub fn format(&self) -> String {
format!(
"mutations: {} total ({} neutral, {} beneficial, {} harmful, {} lethal)",
self.total, self.neutral, self.beneficial, self.harmful, self.lethal
)
}
}
#[derive(Clone, Debug)]
pub struct SmartMutationResult {
pub word_name: String,
pub strategy: MutationStrategy,
pub class: MutationClass,
pub before_hash: u64,
pub after_hash: u64,
pub kept: bool,
pub description: String,
}
pub fn hash_output(s: &str) -> u64 {
let mut h: u64 = 5381;
for b in s.bytes() {
h = h.wrapping_mul(33).wrapping_add(b as u64);
}
h
}
fn now_millis() -> u64 {
#[cfg(not(target_arch = "wasm32"))]
{
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64
}
#[cfg(target_arch = "wasm32")]
{
0
}
}
pub fn is_mutable(entry: &Entry) -> bool {
if entry.hidden || entry.body.is_empty() {
return false;
}
if entry.body.len() == 1 {
if let Instruction::Primitive(_) = &entry.body[0] {
return false;
}
}
true
}
pub fn mutate_entry(
entry: &mut Entry,
rng: &mut SimpleRng,
dictionary_len: usize,
) -> Option<MutationRecord> {
if entry.body.is_empty() {
return None;
}
let original_body = entry.body.clone();
for _ in 0..4 {
let strategy_idx = rng.next_usize(4);
let result = match strategy_idx {
0 => try_constant_tweak(entry, rng),
1 => try_word_swap(entry, rng, dictionary_len),
2 => try_instruction_delete(entry, rng),
3 => try_instruction_dup(entry, rng),
_ => None,
};
if let Some((strategy, description)) = result {
return Some(MutationRecord {
word_name: entry.name.clone(),
word_index: 0, strategy,
original_body,
description,
timestamp: now_millis(),
});
}
entry.body = original_body.clone();
}
entry.body = original_body;
None
}
pub fn undo_mutation(entry: &mut Entry, record: &MutationRecord) {
entry.body = record.original_body.clone();
}
fn try_constant_tweak(
entry: &mut Entry,
rng: &mut SimpleRng,
) -> Option<(MutationStrategy, String)> {
let literals: Vec<usize> = entry
.body
.iter()
.enumerate()
.filter_map(|(i, instr)| match instr {
Instruction::Literal(_) => Some(i),
_ => None,
})
.collect();
if literals.is_empty() {
return None;
}
let idx = literals[rng.next_usize(literals.len())];
if let Instruction::Literal(val) = &mut entry.body[idx] {
let old = *val;
let magnitude = (old.abs() / 10).max(1);
let delta = rng.next_range(-magnitude, magnitude + 1);
*val = old.wrapping_add(delta);
Some((
MutationStrategy::ConstantTweak,
format!("literal {} -> {} at pos {}", old, *val, idx),
))
} else {
None
}
}
fn try_word_swap(
entry: &mut Entry,
rng: &mut SimpleRng,
dictionary_len: usize,
) -> Option<(MutationStrategy, String)> {
if dictionary_len < 2 {
return None;
}
let calls: Vec<usize> = entry
.body
.iter()
.enumerate()
.filter_map(|(i, instr)| match instr {
Instruction::Call(_) => Some(i),
_ => None,
})
.collect();
if calls.is_empty() {
return None;
}
let idx = calls[rng.next_usize(calls.len())];
if let Instruction::Call(old_target) = &entry.body[idx] {
let old_target = *old_target;
let new_target = loop {
let t = rng.next_usize(dictionary_len);
if t != old_target {
break t;
}
};
entry.body[idx] = Instruction::Call(new_target);
Some((
MutationStrategy::WordSwap,
format!("call {} -> {} at pos {}", old_target, new_target, idx),
))
} else {
None
}
}
fn try_instruction_delete(
entry: &mut Entry,
rng: &mut SimpleRng,
) -> Option<(MutationStrategy, String)> {
if entry.body.len() <= 1 {
return None;
}
let idx = rng.next_usize(entry.body.len());
let removed = format!("{:?}", entry.body[idx]);
entry.body.remove(idx);
Some((
MutationStrategy::InstructionDelete,
format!("removed {:?} at pos {}", removed, idx),
))
}
fn try_instruction_dup(
entry: &mut Entry,
rng: &mut SimpleRng,
) -> Option<(MutationStrategy, String)> {
if entry.body.is_empty() {
return None;
}
let idx = rng.next_usize(entry.body.len());
let instr = entry.body[idx].clone();
let desc = format!("duplicated {:?} at pos {}", instr, idx);
entry.body.insert(idx + 1, instr);
Some((MutationStrategy::InstructionDup, desc))
}