use std::sync::Arc;
use doge_compiler as dc;
use doge_runtime::{
finish_pup, pack_snapshot, pack_value, spawn_pup, unpack_packed, DogeError, DogeResult,
ErrorKind, PackMode, Packed, PackedError, Value,
};
use crate::{cell, Interp};
impl Interp {
pub(crate) fn interp_zoom(&mut self, mut args: Vec<Value>) -> DogeResult<Value> {
if args.len() != 2 {
return Err(doge_runtime::function_arity_error(
"pack.zoom",
2,
Some(2),
args.len(),
));
}
let job = args.pop().expect("checked length");
let callee = args.pop().expect("checked length");
if !matches!(
callee,
Value::Function(_) | Value::Class(_) | Value::BoundMethod(_)
) {
return Err(DogeError::type_error(format!(
"pack.zoom needs something callable to run, got {}",
callee.describe()
)));
}
let items = match &job {
Value::List(items) => items.borrow().clone(),
_ => {
return Err(DogeError::type_error(format!(
"pack.zoom needs a List of arguments, got {}",
job.describe()
)))
}
};
let program = match &self.program {
Some(program) => program.clone(),
None => {
return Err(DogeError::new(
ErrorKind::ValueError,
"pack.zoom needs a running program — it isn't available in the repl yet (run the file with doge bark <script>.doge)",
))
}
};
let packed_callee = pack_value(&callee, PackMode::Snapshot)?;
let mut packed_args = Vec::with_capacity(items.len());
for item in &items {
packed_args.push(pack_value(item, PackMode::Transfer)?);
}
let globals = self.snapshot_globals();
spawn_pup(move || run_pup(program, globals, packed_callee, packed_args))
}
fn snapshot_globals(&self) -> Packed {
let globals = self.globals(0);
let globals = globals.borrow();
let pairs = globals
.iter()
.map(|(name, value)| (name.clone(), pack_snapshot(&value.borrow())))
.collect();
Packed::Dict(pairs)
}
fn restore_globals(&mut self, globals: Packed) {
let Packed::Dict(pairs) = globals else {
return;
};
let scope = self.globals(0);
let mut scope = scope.borrow_mut();
for (name, packed) in pairs {
let value = unpack_packed(packed);
match scope.get(&name) {
Some(existing) => *existing.borrow_mut() = value,
None => {
scope.insert(name, cell(value));
}
}
}
}
}
fn run_pup(
program: Arc<dc::Program>,
globals: Packed,
callee: Packed,
args: Vec<Packed>,
) -> Result<Packed, PackedError> {
let mut interp = Interp::new();
interp.integrate_program(&program);
interp.program = Some(program);
interp.restore_globals(globals);
let callee = unpack_packed(callee);
let args: Vec<Value> = args.into_iter().map(unpack_packed).collect();
finish_pup(interp.call_value(callee, args, Vec::new()))
}