parent
f9397f8cac
commit
0153fe4bfb
@ -0,0 +1,209 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::{prelude::Operator, repr::mlog::*};
|
||||||
|
|
||||||
|
/// Represents the instruction pointer
|
||||||
|
#[derive(Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Debug)]
|
||||||
|
pub struct Counter(pub usize);
|
||||||
|
|
||||||
|
impl From<usize> for Counter {
|
||||||
|
fn from(value: usize) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Counter> for usize {
|
||||||
|
fn from(value: Counter) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Counter {
|
||||||
|
pub fn inc(&mut self) {
|
||||||
|
self.0 += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CompiledProgram<'a> {
|
||||||
|
instructions: &'a MindustryProgram,
|
||||||
|
|
||||||
|
/// Points to where the `n`-th (`@counter`-wise) instruction is
|
||||||
|
instruction_indices: Vec<usize>,
|
||||||
|
|
||||||
|
/// Contains the different `@counter` values corresponding to each label
|
||||||
|
labels: HashMap<String, Counter>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CompiledProgram<'a> {
|
||||||
|
pub fn get_instruction(&self, counter: Counter) -> Option<&'a MindustryOperation> {
|
||||||
|
let index = self.instruction_indices.get(counter.0)?;
|
||||||
|
self.instructions.0.get(*index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Default)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum Value {
|
||||||
|
Number(f64),
|
||||||
|
String(String),
|
||||||
|
Symbol(String),
|
||||||
|
#[default]
|
||||||
|
Null,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Value> for f64 {
|
||||||
|
fn from(value: Value) -> f64 {
|
||||||
|
match value {
|
||||||
|
Value::Number(x) => x,
|
||||||
|
Value::String(_) => 1.0,
|
||||||
|
Value::Symbol(_) => 1.0,
|
||||||
|
Value::Null => 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum StopCondition {
|
||||||
|
Steps(usize),
|
||||||
|
Never,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StopCondition {
|
||||||
|
fn should_stop(&self, steps: usize) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Steps(max_steps) => steps >= *max_steps,
|
||||||
|
Self::Never => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile<'a>(program: &'a MindustryProgram) -> CompiledProgram<'a> {
|
||||||
|
let mut instruction_indices = Vec::with_capacity(program.0.len());
|
||||||
|
let mut labels = HashMap::new();
|
||||||
|
let mut current_counter = Counter(0);
|
||||||
|
|
||||||
|
for (index, instr) in program.0.iter().enumerate() {
|
||||||
|
match instr {
|
||||||
|
MindustryOperation::JumpLabel(label) => {
|
||||||
|
labels.insert(label.clone(), current_counter);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
instruction_indices.push(index);
|
||||||
|
|
||||||
|
current_counter.inc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instruction_indices.shrink_to_fit();
|
||||||
|
CompiledProgram {
|
||||||
|
instructions: program,
|
||||||
|
instruction_indices,
|
||||||
|
labels,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(program: &MindustryProgram, stop_condition: StopCondition) -> HashMap<String, Value> {
|
||||||
|
let compiled = compile(program);
|
||||||
|
let mut counter = Counter(0);
|
||||||
|
let mut steps = 0;
|
||||||
|
let mut variables = HashMap::new();
|
||||||
|
|
||||||
|
while !stop_condition.should_stop(steps) {
|
||||||
|
step(&compiled, &mut variables, &mut counter);
|
||||||
|
steps = steps.saturating_add(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
variables
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn step(
|
||||||
|
program: &CompiledProgram<'_>,
|
||||||
|
variables: &mut HashMap<String, Value>,
|
||||||
|
counter: &mut Counter,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let Some(instruction) = program.get_instruction(*counter).or_else(|| {
|
||||||
|
*counter = Counter(0);
|
||||||
|
program.get_instruction(*counter)
|
||||||
|
}) else {
|
||||||
|
// Program is empty
|
||||||
|
return Err(String::from("Program is empty"));
|
||||||
|
};
|
||||||
|
|
||||||
|
match instruction {
|
||||||
|
MindustryOperation::JumpLabel(_) => {
|
||||||
|
unreachable!("Jump label shouldn't be pointed to by `instruction_indices`");
|
||||||
|
}
|
||||||
|
MindustryOperation::Jump(label) => {
|
||||||
|
if let Some(target) = program.labels.get(label) {
|
||||||
|
*counter = *target;
|
||||||
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
counter.inc();
|
||||||
|
return Err(format!("Label {} is invalid", label));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MindustryOperation::JumpIf(label, operation, lhs, rhs) => {
|
||||||
|
if let Some(target) = program.labels.get(label) {
|
||||||
|
if f64::from(eval_operation(*operation, lhs, rhs, variables, counter)) != 0.0f64 {
|
||||||
|
*counter = *target;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
counter.inc();
|
||||||
|
return Err(format!("Label {} is invalid", label));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_operand(operand: &Operand, variables: &HashMap<String, Value>, counter: &Counter) -> Value {
|
||||||
|
match operand {
|
||||||
|
Operand::Variable(name) => {
|
||||||
|
if name == "@counter" {
|
||||||
|
Value::Number(counter.0 as f64)
|
||||||
|
} else {
|
||||||
|
variables.get(name).cloned().unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Operand::String(string) => Value::String(string.clone()),
|
||||||
|
Operand::Integer(int) => Value::Number(*int as f64),
|
||||||
|
Operand::Float(float) => Value::Number(*float),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_operation(
|
||||||
|
op: Operator,
|
||||||
|
lhs: &Operand,
|
||||||
|
rhs: &Operand,
|
||||||
|
variables: &mut HashMap<String, Value>,
|
||||||
|
counter: &mut Counter,
|
||||||
|
) -> Value {
|
||||||
|
let lhs: f64 = eval_operand(lhs, variables, counter).into();
|
||||||
|
let rhs: f64 = eval_operand(rhs, variables, counter).into();
|
||||||
|
|
||||||
|
Value::Number(match op {
|
||||||
|
Operator::Add => lhs + rhs,
|
||||||
|
Operator::Sub => lhs - rhs,
|
||||||
|
Operator::Mul => lhs * rhs,
|
||||||
|
Operator::IDiv => (lhs / rhs).floor(),
|
||||||
|
Operator::Div => lhs / rhs,
|
||||||
|
Operator::Mod => lhs % rhs,
|
||||||
|
Operator::RShift => ((lhs as i64) >> (rhs as i64)) as f64,
|
||||||
|
Operator::LShift => ((lhs as i64) << (rhs as i64)) as f64,
|
||||||
|
Operator::Gt => (lhs > rhs) as u64 as f64,
|
||||||
|
Operator::Lt => (lhs < rhs) as u64 as f64,
|
||||||
|
Operator::Gte => (lhs >= rhs) as u64 as f64,
|
||||||
|
Operator::Lte => (lhs <= rhs) as u64 as f64,
|
||||||
|
Operator::Eq => (lhs == rhs) as u64 as f64,
|
||||||
|
Operator::Neq => (lhs != rhs) as u64 as f64,
|
||||||
|
Operator::Max => lhs.max(rhs),
|
||||||
|
Operator::Min => lhs.min(rhs),
|
||||||
|
Operator::Pow => lhs.powf(rhs),
|
||||||
|
Operator::And => (lhs != 0.0 && rhs != 0.0) as u64 as f64,
|
||||||
|
Operator::Or => (lhs != 0.0 || rhs != 0.0) as u64 as f64,
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in new issue