lucia-lang 0.2.0

Lucia programming language
Documentation
use std::{
    cell::RefMut,
    cmp::Ordering,
    fmt::{self, Debug},
    hash::{Hash, Hasher},
};

use gc_arena::{lock::RefLock, Collect, Gc, Mutation};

use crate::{
    errors::{Error, ErrorKind},
    objects::{AnyCallback, CallbackReturn, Closure, Function, IntoValue, Table, Value},
    Context,
};

#[derive(Clone, Copy, Collect)]
#[collect(no_drop)]
pub struct Frames<'gc>(pub(crate) Gc<'gc, RefLock<FramesState<'gc>>>);

impl<'gc> Debug for Frames<'gc> {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.debug_tuple("Frames")
            .field(&(&self.0 as *const _))
            .finish()
    }
}

impl<'gc> PartialEq for Frames<'gc> {
    fn eq(&self, other: &Frames<'gc>) -> bool {
        Gc::ptr_eq(self.0, other.0)
    }
}

impl<'gc> Eq for Frames<'gc> {}

impl<'gc> Hash for Frames<'gc> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.0.as_ptr().hash(state)
    }
}

#[derive(Debug, Clone, Copy, Collect, PartialEq, Eq, Hash)]
#[collect[require_static]]
pub enum FrameMode {
    // No frames are on the thread and there are no available results, the thread can be started.
    Stopped,
    // Frames has an active Lua frame or is waiting for a callback or sequence to finish.
    Normal,
    // Frames is currently inside its own `Thread::step` function.
    Running,
}

impl fmt::Display for FrameMode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

impl<'gc> Frames<'gc> {
    pub fn new(mc: &Mutation<'gc>) -> Frames<'gc> {
        Frames(Gc::new(
            mc,
            RefLock::new(FramesState {
                frames: Vec::new(),
                return_value: Value::Null,
            }),
        ))
    }

    pub fn mode(self) -> FrameMode {
        if let Ok(state) = self.0.try_borrow() {
            state.mode()
        } else {
            FrameMode::Running
        }
    }

    /// If this thread is `Stopped`, start a new function with the given arguments.
    pub fn start(
        self,
        ctx: Context<'gc>,
        function: Function<'gc>,
        args: Vec<Value<'gc>>,
    ) -> Result<(), Error<'gc>> {
        let mut state = self.check_mode(&ctx, FrameMode::Stopped)?;
        state.call_function(ctx, function, args)?;
        Ok(())
    }

    /// If the thread is in `Normal` mode, either run the Lua VM for a while or step any callback
    /// that we are waiting on.
    pub fn step(self, ctx: Context<'gc>) -> Result<(), Error<'gc>> {
        let mut state = self.check_mode(&ctx, FrameMode::Normal)?;

        match state.frames.last().expect("no frame to step") {
            Frame::Callback(..) => {
                if let Frame::Callback(callback, args) = state.frames.pop().unwrap() {
                    state.frames.push(Frame::Calling);

                    drop(state);
                    let return_value = callback.call(ctx, args);
                    let mut state = self.0.borrow_mut(&ctx);

                    assert!(
                        matches!(state.frames.pop(), Some(Frame::Calling)),
                        "thread state has changed while callback was run"
                    );

                    match return_value {
                        Ok(CallbackReturn::Return(v)) => match state.frames.last_mut() {
                            Some(Frame::Lua(LuciaFrame { stack, .. })) => stack.push(v),
                            _ => panic!("frame above callback must be lua frame"),
                        },
                        Ok(CallbackReturn::TailCall(f, args)) => {
                            if let Err(e) = state.call_function(ctx, f, args) {
                                state.return_error(ctx, e);
                            }
                        }
                        Err(e) => state.return_error(ctx, e),
                    }
                }
            }
            Frame::Lua { .. } => {
                const VM_GRANULARITY: u32 = 256;
                let mut instructions = VM_GRANULARITY;

                loop {
                    match state.run_vm(ctx, instructions) {
                        Ok(i) => {
                            if let Some(Frame::Lua { .. }) = state.frames.last() {
                                instructions = i;
                                if instructions == 0 {
                                    break;
                                }
                            } else {
                                break;
                            }
                        }
                        Err(e) => {
                            state.return_error(ctx, e);
                            break;
                        }
                    }
                }
            }
            _ => panic!("tried to step invalid frame type"),
        }

        Ok(())
    }

    fn check_mode<'a>(
        &'a self,
        mc: &Mutation<'gc>,
        expected: FrameMode,
    ) -> Result<RefMut<'a, FramesState<'gc>>, Error<'gc>> {
        assert!(expected != FrameMode::Running);
        let state = self.0.try_borrow_mut(mc).map_err(|_| {
            Error::new(ErrorKind::BadFrameMode {
                expected,
                found: FrameMode::Running,
            })
        })?;

        let found = state.mode();
        if found != expected {
            Err(Error::new(ErrorKind::BadFrameMode { expected, found }))
        } else {
            Ok(state)
        }
    }
}

#[derive(Collect)]
#[collect(no_drop)]
pub(crate) struct FramesState<'gc> {
    pub frames: Vec<Frame<'gc>>,
    pub return_value: Value<'gc>,
}

#[derive(Collect, Debug, Clone)]
#[collect(no_drop)]
pub struct LuciaFrame<'gc> {
    pub pc: usize,
    pub closure: Closure<'gc>,
    pub locals: Vec<Value<'gc>>,
    pub stack: Vec<Value<'gc>>,
    pub catch_error: bool,
}

#[derive(Collect, Debug, Clone)]
#[collect(no_drop)]
pub enum Frame<'gc> {
    // An running Lua frame.
    Lua(LuciaFrame<'gc>),
    // A callback that has been queued but not called yet. Arguments will be in the external stack.
    Callback(AnyCallback<'gc>, Vec<Value<'gc>>),
    // The thread must be unlocked during external calls to permit cross-thread upvalue handling,
    // but this presents a danger if methods on this thread were to be recursively called at this
    // time. This frame keeps the thread in the `Running` mode during external calls, ensuring the
    // thread cannot be mutated.
    Calling,
}

impl<'gc> LuciaFrame<'gc> {
    pub(crate) fn new(
        ctx: Context<'gc>,
        closure: Closure<'gc>,
        mut args: Vec<Value<'gc>>,
    ) -> Result<Self, Error<'gc>> {
        let function = &closure.0.function;
        let params_num = function.params.len();
        let mut stack = vec![Value::Null; params_num];
        match args.len().cmp(&params_num) {
            Ordering::Less => {
                return Err(Error::new(ErrorKind::CallArguments {
                    value: Some(closure),
                    required: if function.variadic.is_none() {
                        params_num.into()
                    } else {
                        (params_num, None).into()
                    },
                    given: args.len(),
                }));
            }
            Ordering::Equal => {
                stack[..params_num].copy_from_slice(&args[..]);
                if function.variadic.is_some() {
                    stack.push(Value::Table(Table::new(&ctx)));
                }
            }
            Ordering::Greater => {
                if function.variadic.is_none() {
                    return Err(Error::new(ErrorKind::CallArguments {
                        value: Some(closure),
                        required: params_num.into(),
                        given: args.len(),
                    }));
                } else {
                    let t = args.split_off(params_num);
                    stack[..params_num].copy_from_slice(&args[..]);
                    stack.push(t.into_value(ctx));
                }
            }
        }
        Ok(LuciaFrame {
            pc: 0,
            closure,
            locals: vec![Value::Null; function.local_names.len()],
            stack,
            catch_error: false,
        })
    }
}

impl<'gc> FramesState<'gc> {
    fn mode(&self) -> FrameMode {
        match self.frames.last() {
            None => FrameMode::Stopped,
            Some(frame) => match frame {
                Frame::Lua { .. } | Frame::Callback { .. } => FrameMode::Normal,
                Frame::Calling => FrameMode::Running,
            },
        }
    }

    pub(crate) fn call_function(
        &mut self,
        ctx: Context<'gc>,
        function: Function<'gc>,
        args: Vec<Value<'gc>>,
    ) -> Result<(), Error<'gc>> {
        self.frames.push(match function {
            Function::Closure(closure) => Frame::Lua(LuciaFrame::new(ctx, closure, args)?),
            Function::Callback(callback) => Frame::Callback(callback, args),
        });
        Ok(())
    }

    pub(crate) fn tail_call(
        &mut self,
        ctx: Context<'gc>,
        function: Function<'gc>,
        args: Vec<Value<'gc>>,
    ) -> Result<(), Error<'gc>> {
        *self.frames.last_mut().expect("top frame is not lua frame") = match function {
            Function::Closure(closure) => Frame::Lua(LuciaFrame::new(ctx, closure, args)?),
            Function::Callback(callback) => Frame::Callback(callback, args),
        };
        Ok(())
    }

    // Return to the upper frame with results starting at the given register index.
    pub(crate) fn return_upper(&mut self) {
        match self.frames.pop() {
            Some(Frame::Lua(LuciaFrame { mut stack, .. })) => {
                let return_value = stack.pop().expect("stack error");
                match self.frames.last_mut() {
                    Some(Frame::Lua(LuciaFrame { stack, .. })) => stack.push(return_value),
                    None => self.return_value = return_value,
                    _ => panic!("lua frame must be above a lua frame"),
                }
            }
            _ => panic!("top frame is not lua frame"),
        }
    }

    pub(crate) fn return_error(&mut self, ctx: Context<'gc>, mut e: Error<'gc>) {
        if e.traceback.is_none() {
            e.traceback = Some(self.traceback());
        }
        if e.kind.recoverable() {
            for (c, f) in self.frames.iter_mut().rev().enumerate() {
                if let Frame::Lua(LuciaFrame {
                    catch_error: true,
                    stack,
                    ..
                }) = f
                {
                    stack.push(e.into_value(ctx));
                    self.frames.truncate(c);
                    return;
                }
            }
        }
        self.frames.clear();
        self.return_value = e.into_value(ctx);
    }

    pub(crate) fn traceback(&self) -> Vec<Frame<'gc>> {
        self.frames.clone()
    }
}