nilang 0.4.1

A scripting language interpreter for Advent of Code
Documentation
use sandpit::{field, Gc, GcOpt, Mutator, Trace};

use crate::spanned::GcPackedSpans;
use crate::runtime::string::VMString;

use super::tagged_value::{TaggedValue, ValueTag};
use super::value::Value;
use super::vm::ByteCode;

#[derive(Trace)]
pub enum LoadedLocal<'gc> {
    Func(Gc<'gc, Func<'gc>>),
    SymId(u32),
    Int(i64),
    Float(f64),
    Text(Gc<'gc, [char]>),
}

impl<'gc> LoadedLocal<'gc> {
    pub fn as_value(&self, mu: &'gc Mutator) -> Value<'gc> {
        match self {
            LoadedLocal::SymId(s) => Value::SymId(*s),
            LoadedLocal::Int(i) => Value::Int(*i),
            LoadedLocal::Float(f) => Value::Float(*f),
            LoadedLocal::Func(f) => Value::Func(f.clone()),
            LoadedLocal::Text(gc_text) => Value::String(Gc::new(mu, VMString::alloc(gc_text.iter().copied(), mu))),
        }
    }
}

#[derive(Trace, Clone)]
pub struct Func<'gc> {
    arity: u8,
    max_clique: u8,
    locals: Gc<'gc, [LoadedLocal<'gc>]>,
    code: Gc<'gc, [ByteCode]>,
    upvalues: GcOpt<'gc, [TaggedValue<'gc>]>,
    bound_args: GcOpt<'gc, [TaggedValue<'gc>]>,
    
    // can be moved into meta data ptr
    auto_binds: bool,
    spans: Option<GcPackedSpans<'gc>>,
    file_path: Option<Gc<'gc, VMString<'gc>>>,
    id: u32,
    top_level: bool,
}

impl<'gc> Func<'gc> {
    pub fn create_closure(
        &self,
        upvalues: GcOpt<'gc, [TaggedValue<'gc>]>,
        recursive_upval_idx: Option<usize>,
        mu: &'gc Mutator<'gc>
    ) -> Gc<'gc, Self> {
        let closure = Self {
            id: self.id,
            auto_binds: self.auto_binds,
            arity: self.arity,
            max_clique: self.max_clique,
            locals: self.locals.clone(),
            code: self.code.clone(),
            spans: self.spans.clone(),
            file_path: self.file_path.clone(),
            top_level: self.top_level,
            bound_args: GcOpt::new_none(),
            upvalues,
        };

        let closure_ptr = Gc::new(mu, closure);

        if let Some(idx) = recursive_upval_idx {
            closure_ptr.upvalues.unwrap().write_barrier(mu, |barrier| {
                let recursive_upval = barrier.at(idx);
                let recursive_upval = field!(&recursive_upval, TaggedValue, ptr);

                recursive_upval.set(ValueTag::from_func(closure_ptr.clone()));
            });
        }

        closure_ptr
    }

    pub fn new(
        id: u32,
        auto_binds: bool,
        arity: u8,
        max_clique: u8,
        locals: Gc<'gc, [LoadedLocal<'gc>]>,
        code: Gc<'gc, [ByteCode]>,
        upvalues: GcOpt<'gc, [TaggedValue<'gc>]>,
        bound_args: GcOpt<'gc, [TaggedValue<'gc>]>,
        spans: Option<GcPackedSpans<'gc>>,
        file_path: Option<Gc<'gc, VMString<'gc>>>,
        top_level: bool
    ) -> Self {
        Self {
            id,
            auto_binds,
            arity,
            max_clique,
            locals,
            code,
            spans,
            file_path,
            top_level,
            upvalues,
            bound_args
        }
    }

    pub fn get_id(&self) -> u32 {
        self.id
    }

    pub fn get_max_clique(&self) -> u8 {
        self.max_clique
    }

    pub fn arity(&self) -> u8 {
        self.arity - self.bound_args()
    }

    pub fn bound_args(&self) -> u8 {
        if let Some(bound_args) = self.bound_args.as_option() {
            bound_args.len() as u8
        } else {
            0
        }
    }

    pub fn bind(&self,
        mu: &'gc Mutator<'gc>,
        tagged_val: TaggedValue<'gc>,
    ) -> Result<Gc<'gc, Self>, super::error::RuntimeError> {
        let new_bound_args_count = self.bound_args() + 1;

        // Check that we're not binding more arguments than the function accepts
        if new_bound_args_count > self.arity {
            return Err(super::error::RuntimeError::new(
                super::error::RuntimeErrorKind::InvalidBind,
                Some(format!(
                    "Attempted to bind {} arguments to a function with arity {}",
                    new_bound_args_count,
                    self.arity
                )),
                None
            ));
        }

        let bound_args = mu.alloc_array_from_fn(new_bound_args_count as usize, |idx| {
            if idx == self.bound_args() as usize {
                tagged_val.clone()
            } else {
                self.bound_args.unwrap()[idx].clone()
            }
        });

        let partial = Self {
            id: self.id,
            auto_binds: false,
            arity: self.arity,
            max_clique: self.max_clique,
            locals: self.locals.clone(),
            code: self.code.clone(),
            spans: self.spans.clone(),
            file_path: self.file_path.clone(),
            top_level: self.top_level,
            upvalues: self.upvalues.clone(),
            bound_args: GcOpt::from(bound_args),
        };

        Ok(Gc::new(mu, partial))
    }

    pub fn update_locals(
        this: Gc<'gc, Self>,
        new_locals: Gc<'gc, [LoadedLocal<'gc>]>,
        mu: &'gc Mutator,
    ) {
        this.write_barrier(mu, |barrier| {
            let old_locals = field!(barrier, Func, locals);
            old_locals.set(new_locals);
        });
    }

    pub fn get_local(&self, i: usize, mu: &'gc Mutator) -> Value<'gc> {
        self.locals[i].as_value(mu)
    }

    pub fn get_upvalue(&self, i: usize) -> TaggedValue<'gc> {
        self.upvalues.unwrap()[i].clone()
    }

    pub fn get_bound_args(&self) -> Option<Gc<'gc, [TaggedValue<'gc>]>> {
        self.bound_args.as_option()
    }

    pub fn get_code(&self) -> Gc<'gc, [ByteCode]> {
        self.code.clone()
    }

    pub fn get_spans(&self) -> GcPackedSpans<'_> {
        self.spans.clone().unwrap()
    }

    pub fn get_file_path(&self) -> Option<Gc<'gc, VMString<'gc>>> {
        self.file_path.clone()
    }

    pub fn auto_binds(&self) -> bool {
        self.auto_binds
    }
}