onion-vm 0.1.0

Virtual machine runtime for the Onion programming language with async execution and garbage collection
Documentation
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>), // Normal frame
}

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);
    }
}