use rustc_hash::FxHashMap as HashMap;
use crate::{
lambda::runnable::RuntimeError,
types::object::{ObjectError, OnionStaticObject},
};
#[derive(Clone, Debug)]
pub enum Frame {
Normal(HashMap<String, OnionStaticObject>, Vec<OnionStaticObject>), }
impl Frame {
pub fn get_stack(&self) -> &Vec<OnionStaticObject> {
match self {
Frame::Normal(_, stack) => stack,
}
}
pub fn get_stack_mut(&mut self) -> &mut Vec<OnionStaticObject> {
match self {
Frame::Normal(_, stack) => stack,
}
}
}
#[derive(Clone)]
pub struct Context {
pub(crate) frames: Vec<Frame>,
}
impl Context {
pub fn new() -> Self {
Context { frames: Vec::new() }
}
pub fn push_frame(&mut self, frame: Frame) {
self.frames.push(frame);
}
pub fn pop_frame(&mut self) -> Result<Frame, RuntimeError> {
match self.frames.pop() {
Some(frame) => Ok(frame),
None => Err(RuntimeError::DetailedError(
"Cannot pop frame from empty context".to_string(),
)),
}
}
pub fn concat_last_frame(&mut self) -> Result<(), RuntimeError> {
if self.frames.len() < 2 {
return Ok(());
}
let last_frame = self.frames.pop().unwrap();
let second_last_frame = self.frames.last_mut().unwrap();
match second_last_frame {
Frame::Normal(_, stack) => {
match last_frame {
Frame::Normal(_, last_stack) => {
stack.extend(last_stack);
}
}
}
}
Ok(())
}
pub fn clear_stack(&mut self) {
if self.frames.len() > 0 {
self.frames.last_mut().unwrap().get_stack_mut().clear();
}
}
pub fn push_object(&mut self, object: OnionStaticObject) -> Result<(), RuntimeError> {
if self.frames.len() > 0 {
self.frames
.last_mut()
.unwrap()
.get_stack_mut()
.push(object);
Ok(())
} else {
Err(RuntimeError::DetailedError(
"Cannot push object to empty context".to_string(),
))
}
}
pub fn pop(&mut self) -> Result<OnionStaticObject, RuntimeError> {
if self.frames.len() == 0 {
return Err(RuntimeError::DetailedError(
"Cannot pop object from empty context".to_string(),
));
}
let last_frame = self.frames.last_mut().unwrap();
if last_frame.get_stack().len() == 0 {
return Err(RuntimeError::DetailedError(
"Cannot pop object from empty stack".to_string(),
));
}
let stack = last_frame.get_stack_mut();
Ok(stack.pop().unwrap())
}
pub fn discard_objects(&mut self, count: usize) -> Result<(), RuntimeError> {
if self.frames.len() == 0 {
return Err(RuntimeError::DetailedError(
"Cannot discard objects from empty context".to_string(),
));
}
let last_frame = self.frames.last_mut().unwrap();
let stack = last_frame.get_stack_mut();
if stack.len() < count {
return Err(RuntimeError::DetailedError(
"Cannot discard more objects than available in stack".to_string(),
));
}
for _ in 0..count {
stack.pop();
}
Ok(())
}
pub fn discard_objects_offset(
&mut self,
offset: usize,
count: usize,
) -> Result<(), RuntimeError> {
if self.frames.len() == 0 {
return Err(RuntimeError::DetailedError(
"Cannot discard objects from empty context".to_string(),
));
}
let last_frame = self.frames.last_mut().unwrap();
let stack = last_frame.get_stack_mut();
if stack.len() < offset + count {
return Err(RuntimeError::DetailedError(
"Cannot discard more objects than available in stack".to_string(),
));
}
for _ in 0..count {
stack.remove(stack.len() - 1 - offset);
}
Ok(())
}
pub fn get_object_rev(&self, idx: usize) -> Result<&OnionStaticObject, RuntimeError> {
if self.frames.len() == 0 {
return Err(RuntimeError::DetailedError(
"Cannot get object from empty context".to_string(),
));
}
let last_frame = self.frames.last().unwrap();
if last_frame.get_stack().len() <= idx {
return Err(RuntimeError::DetailedError(
"Cannot get object from empty stack".to_string(),
));
}
let stack = last_frame.get_stack();
match stack.get(stack.len() - 1 - idx) {
None => Err(RuntimeError::DetailedError(
"Index out of bounds".to_string(),
)),
Some(o) => Ok(o)
}
}
pub fn get_object_rev_mut(
&mut self,
idx: usize,
) -> Result<&mut OnionStaticObject, RuntimeError> {
if self.frames.len() == 0 {
return Err(RuntimeError::DetailedError(
"Cannot get object from empty context".to_string(),
));
}
let last_frame = self.frames.last_mut().unwrap();
if last_frame.get_stack().len() <= idx {
return Err(RuntimeError::DetailedError(
"Cannot get object from empty stack".to_string(),
));
}
let stack = last_frame.get_stack_mut();
let idx = stack.len() - 1 - idx;
match stack.get_mut(idx) {
None => Err(RuntimeError::DetailedError(
"Index out of bounds".to_string(),
)),
Some(o) => Ok(o)
}
}
pub fn let_variable(
&mut self,
name: &String,
value: OnionStaticObject,
) -> Result<(), ObjectError> {
if self.frames.len() == 0 {
return Err(ObjectError::InvalidOperation(
"Cannot let variable in empty context".to_string(),
));
}
let last_frame = self.frames.last_mut().unwrap();
match last_frame {
Frame::Normal(vars, _) => {
vars.insert(name.clone(), value);
}
}
Ok(())
}
pub fn get_variable(&self, name: &String) -> Result<&OnionStaticObject, RuntimeError> {
if self.frames.len() == 0 {
return Err(RuntimeError::DetailedError(
"Cannot get variable from empty context".to_string(),
));
}
for frame in self.frames.iter().rev() {
match frame {
Frame::Normal(vars, _) => {
if let Some(value) = vars.get(name) {
return Ok(value);
}
}
}
}
Err(RuntimeError::DetailedError(format!(
"Variable `{}` not found",
name
)))
}
fn _debug_print(&self) {
println!("Context Debug Print:");
for (i, frame) in self.frames.iter().enumerate() {
println!("Frame {}: {:?}", i, frame);
}
}
pub fn get_variable_mut(
&mut self,
name: &String,
) -> Result<&mut OnionStaticObject, RuntimeError> {
if self.frames.len() == 0 {
return Err(RuntimeError::DetailedError(
"Cannot get variable from empty context".to_string(),
));
}
for frame in self.frames.iter_mut().rev() {
match frame {
Frame::Normal(vars, _) => {
if let Some(value) = vars.get_mut(name) {
return Ok(value);
}
}
}
}
Err(RuntimeError::DetailedError(format!(
"Variable `{}` not found",
name
)))
}
pub fn swap(&mut self, idx1: usize, idx2: usize) -> Result<(), RuntimeError> {
if self.frames.len() == 0 {
return Err(RuntimeError::DetailedError(
"Cannot swap objects in empty context".to_string(),
));
}
let last_frame = self.frames.last_mut().unwrap();
let stack = last_frame.get_stack_mut();
if stack.len() <= idx1 || stack.len() <= idx2 {
return Err(RuntimeError::DetailedError(
"Cannot swap objects in empty stack".to_string(),
));
}
let temp = stack[idx1].clone();
stack[idx1] = stack[idx2].clone();
stack[idx2] = temp;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::object::OnionObject;
use std::time::Instant;
use arc_gc::gc::GC;
#[test]
fn benchmark() {
let mut context = Context::new();
let frame = Frame::Normal(HashMap::default(), Vec::new());
context.push_frame(frame);
let mut gc = GC::new();
context.let_variable(&"i".to_string(), OnionObject::Integer(0).stabilize().mutablize(&mut gc).unwrap()).unwrap();
let start = Instant::now();
for _ in 0..30_000_000 {
let _ = context.get_variable_mut(&"i".to_string()).unwrap().clone();
}
let duration = start.elapsed();
println!("Time taken for 30,000,000 iterations: {:?}", duration);
}
}