use crate::push::instructions::{InstructionCache, InstructionSet};
use crate::push::item::{Item, PushType};
use crate::push::state::PushState;
use std::time::{Duration, Instant};
#[derive(Debug, PartialEq)]
pub enum PushInterpreterState {
NoErrors,
StepLimitExceeded,
TimeLimitExceeded,
GrowthCapExceeded,
}
pub struct PushInterpreter {}
impl PushInterpreter {
pub fn copy_to_code_stack(push_state: &mut PushState) {
if let Some(code) = push_state.exec_stack.copy_vec(push_state.exec_stack.size()) {
push_state.code_stack.push_vec(code);
}
}
pub fn step(
push_state: &mut PushState,
instruction_set: &mut InstructionSet,
icache: &InstructionCache,
) -> bool {
match push_state.exec_stack.pop() {
None => true,
Some(Item::Literal { push_type }) => {
match push_type {
PushType::Bool { val } => push_state.bool_stack.push(val),
PushType::Int { val } => push_state.int_stack.push(val),
PushType::Index { val } => push_state.index_stack.push(val),
PushType::Float { val } => push_state.float_stack.push(val),
PushType::BoolVector { val } => push_state.bool_vector_stack.push(val),
PushType::FloatVector { val } => push_state.float_vector_stack.push(val),
PushType::IntVector { val } => push_state.int_vector_stack.push(val),
PushType::Graph { val } => push_state.graph_stack.push(val),
}
false
}
Some(Item::Identifier { name }) => {
if push_state.quote_name {
push_state.name_stack.push(name);
push_state.quote_name = false;
} else {
if let Some(item) = push_state.name_bindings.get(&*name) {
push_state.exec_stack.push(item.clone());
} else {
push_state.name_stack.push(name);
}
}
false
}
Some(Item::InstructionMeta { name }) => {
if let Some(instruction) = instruction_set.get_instruction(&name) {
(instruction.execute)(push_state, &icache);
}
false
}
Some(Item::List { mut items }) => {
if let Some(pv) = items.pop_vec(items.size()) {
push_state.exec_stack.push_vec(pv);
}
false
}
}
}
pub fn run(
push_state: &mut PushState,
instruction_set: &mut InstructionSet,
) -> PushInterpreterState {
PushInterpreter::copy_to_code_stack(push_state);
let icache = instruction_set.cache();
let mut step_counter = 0;
let start = Instant::now();
loop {
if step_counter > push_state.configuration.eval_push_limit {
return PushInterpreterState::StepLimitExceeded;
}
if start.elapsed() > Duration::from_millis(push_state.configuration.eval_time_limit) {
return PushInterpreterState::TimeLimitExceeded;
}
let size_before_step = push_state.size();
if PushInterpreter::step(push_state, instruction_set, &icache) {
break;
}
if push_state.size() > size_before_step + push_state.configuration.growth_cap as usize {
return PushInterpreterState::GrowthCapExceeded;
}
step_counter += 1;
}
PushInterpreterState::NoErrors
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::push::parser::PushParser;
pub fn icache() -> InstructionCache {
InstructionCache::new(vec![])
}
#[test]
pub fn copy_simple_program_to_code_stack() {
let input = "( 2 3 INTEGER.* 4.1 5.2 FLOAT.+ TRUE FALSE BOOLEAN.OR )";
let mut push_state = PushState::new();
let mut instruction_set = InstructionSet::new();
instruction_set.load();
PushParser::parse_program(&mut push_state, &instruction_set, &input);
PushInterpreter::copy_to_code_stack(&mut push_state);
assert_eq!(push_state.code_stack.to_string(), "( 2 3 INTEGER.* 4.100 5.200 FLOAT.+ TRUE FALSE BOOLEAN.OR )");
}
#[test]
pub fn run_simple_program() {
let mut push_state = PushState::new();
let mut instruction_set = InstructionSet::new();
instruction_set.load();
push_state
.exec_stack
.push(Item::instruction("BOOLEAN.OR".to_string()));
push_state.exec_stack.push(Item::bool(false));
push_state.exec_stack.push(Item::bool(true));
push_state
.exec_stack
.push(Item::instruction("FLOAT.+".to_string()));
push_state.exec_stack.push(Item::float(5.2));
push_state.exec_stack.push(Item::float(4.1));
push_state
.exec_stack
.push(Item::instruction("INTEGER.*".to_string()));
push_state.exec_stack.push(Item::int(3));
push_state.exec_stack.push(Item::int(2));
assert_eq!(push_state.exec_stack.to_string(), "2 3 INTEGER.* 4.100 5.200 FLOAT.+ TRUE FALSE BOOLEAN.OR");
assert_eq!(
PushInterpreter::run(&mut push_state, &mut instruction_set),
PushInterpreterState::NoErrors
);
assert_eq!(push_state.int_stack.to_string(), "6");
assert!((push_state.float_stack.copy_vec(1).unwrap()[0] - 9.3).abs() < 0.00001);
assert_eq!(push_state.bool_stack.to_string(), "TRUE");
}
#[test]
pub fn run_potentiation_program() {
let input = "( ARG FLOAT.DEFINE EXEC.Y ( ARG FLOAT.* 1 INTEGER.- INTEGER.DUP 0 INTEGER.> EXEC.IF ( ) EXEC.POP ) ) ";
let mut push_state = PushState::new();
let mut instruction_set = InstructionSet::new();
instruction_set.load();
PushParser::parse_program(&mut push_state, &instruction_set, &input);
push_state.int_stack.push(4);
push_state.float_stack.push(2.0);
assert_eq!(
PushInterpreter::run(&mut push_state, &mut instruction_set),
PushInterpreterState::NoErrors
);
assert_eq!(push_state.float_stack.to_string(), "16.0");
}
#[test]
pub fn run_factorial_program() {
let input = "( CODE.QUOTE ( INTEGER.POP 1 )
CODE.QUOTE ( CODE.DUP INTEGER.DUP 1 INTEGER.- CODE.DO INTEGER.* )
INTEGER.DUP 2 INTEGER.< CODE.IF )";
let mut push_state = PushState::new();
let mut instruction_set = InstructionSet::new();
instruction_set.load();
PushParser::parse_program(&mut push_state, &instruction_set, &input);
push_state.int_stack.push(4);
assert_eq!(
PushInterpreter::run(&mut push_state, &mut instruction_set),
PushInterpreterState::NoErrors
);
assert_eq!(push_state.int_stack.to_string(), "24");
}
#[test]
pub fn run_execution_loop() {
let input = "( 0 4 INDEX.DEFINE EXEC.LOOP ( INDEX.CURRENT INTEGER.+ ) )";
let mut push_state = PushState::new();
let mut instruction_set = InstructionSet::new();
instruction_set.load();
PushParser::parse_program(&mut push_state, &instruction_set, &input);
loop {
if PushInterpreter::step(&mut push_state, &mut instruction_set, &icache()) {
break;
}
}
assert_eq!(push_state.int_stack.to_string(), "6");
assert_eq!(push_state.index_stack.to_string(), "");
assert_eq!(push_state.exec_stack.to_string(), "");
}
#[test]
fn run_execution_loop_with_zero_length() {
let input = "( 0 0 INDEX.DEFINE EXEC.LOOP ( INDEX.CURRENT INTEGER.+ ) )";
let mut push_state = PushState::new();
let mut instruction_set = InstructionSet::new();
instruction_set.load();
PushParser::parse_program(&mut push_state, &instruction_set, &input);
loop {
if PushInterpreter::step(&mut push_state, &mut instruction_set, &icache()) {
break;
}
}
assert_eq!(push_state.int_stack.to_string(), "0");
assert_eq!(push_state.index_stack.to_string(), "");
assert_eq!(push_state.exec_stack.to_string(), "");
}
#[test]
pub fn run_int_vector_loop() {
let input = "( 0 INT[2,3,4] INTVECTOR.LOOP ( INTEGER.+ ) )";
let mut push_state = PushState::new();
let mut instruction_set = InstructionSet::new();
instruction_set.load();
PushParser::parse_program(&mut push_state, &instruction_set, &input);
loop {
if PushInterpreter::step(&mut push_state, &mut instruction_set, &icache()) {
break;
}
}
assert_eq!(push_state.int_stack.to_string(), "9");
assert_eq!(push_state.int_vector_stack.to_string(), "");
assert_eq!(push_state.exec_stack.to_string(), "");
}
#[test]
fn run_int_vector_loop_with_zero_length() {
let input = "( 0 INT[] INTVECTOR.LOOP ( INTEGER.+ ) )";
let mut push_state = PushState::new();
let mut instruction_set = InstructionSet::new();
instruction_set.load();
PushParser::parse_program(&mut push_state, &instruction_set, &input);
loop {
if PushInterpreter::step(&mut push_state, &mut instruction_set, &icache()) {
break;
}
}
assert_eq!(push_state.int_stack.to_string(), "0");
assert_eq!(push_state.index_stack.to_string(), "");
assert_eq!(push_state.exec_stack.to_string(), "");
}
}