use super::{
Instruction, LambdaTemplate, bytecode::Bytecode, bytecode::CompiledDefun, bytecode::TraceRange,
compiler::VMDefunParams,
};
use crate::{
Error, Number, TulispContext, TulispObject, TulispValue, bytecode::Pos,
object::wrappers::generic::SharedMut, plist,
};
use std::collections::HashMap;
struct TailCallInfo {
function: CompiledDefun,
optional_count: usize,
rest_count: usize,
}
#[inline(always)]
fn binary_op_checked(
a: &TulispObject,
b: &TulispObject,
op: impl FnOnce(Number, Number) -> Result<Number, Error>,
) -> Result<TulispObject, Error> {
let a = a.as_number()?;
let b = b.as_number()?;
op(a, b).map(Into::into)
}
#[inline(always)]
fn compare_op(
a: &TulispObject,
b: &TulispObject,
cmp: impl FnOnce(Number, Number) -> bool,
) -> Result<bool, Error> {
let a = a.as_number()?;
let b = b.as_number()?;
Ok(cmp(a, b))
}
struct SetParams(Vec<TulispObject>);
impl SetParams {
fn new() -> Self {
Self(Vec::new())
}
fn push(&mut self, obj: TulispObject) {
self.0.push(obj);
}
}
impl Drop for SetParams {
fn drop(&mut self) {
for obj in self.0.iter() {
let _ = obj.unset();
}
}
}
struct ActiveScopes(Vec<TulispObject>);
impl ActiveScopes {
fn new() -> Self {
Self(Vec::new())
}
fn enter(&mut self, obj: TulispObject) {
self.0.push(obj);
}
fn exit(&mut self, obj: &TulispObject) {
if let Some(pos) = self.0.iter().rposition(|x| x.eq_ptr(obj)) {
self.0.remove(pos);
}
}
}
impl Drop for ActiveScopes {
fn drop(&mut self) {
for obj in self.0.iter().rev() {
let _ = obj.unset();
}
}
}
pub struct Machine {
stack: Vec<TulispObject>,
bytecode: Bytecode,
labels: HashMap<usize, usize>, }
macro_rules! jump_to_pos {
($ctx: ident, $pc:ident, $pos:ident) => {
$pc = {
match $pos {
Pos::Abs(p) => *p,
Pos::Rel(p) => {
let abs_pos = ($pc as isize + *p + 1) as usize;
*$pos = Pos::Abs(abs_pos);
abs_pos
}
Pos::Label(p) => {
let abs_pos = *$ctx.vm.labels.get(&p.addr_as_usize()).unwrap();
*$pos = Pos::Abs(abs_pos); abs_pos
}
}
}
};
}
impl Machine {
pub(crate) fn new() -> Self {
Machine {
stack: Vec::new(),
bytecode: Bytecode::new(),
labels: HashMap::new(),
}
}
}
fn locate_labels(bytecode: &Bytecode) -> HashMap<usize, usize> {
let mut labels = HashMap::new();
for (i, instr) in bytecode.global.borrow().iter().enumerate() {
if let Instruction::Label(name) = instr {
labels.insert(name.addr_as_usize(), i + 1);
}
}
for func in bytecode.functions.values() {
for (i, instr) in func.instructions.borrow().iter().enumerate() {
if let Instruction::Label(name) = instr {
labels.insert(name.addr_as_usize(), i + 1);
}
}
}
labels
}
#[allow(dead_code)]
fn print_stack(ctx: &TulispContext, func: Option<usize>, pc: usize, recursion_depth: u32) {
println!("Stack:");
for obj in ctx.vm.stack.iter() {
println!(" {}", obj);
}
println!(
"\nDepth: {}: PC: {}; Executing: {}",
recursion_depth,
pc,
if let Some(func) = func {
ctx.vm
.bytecode
.functions
.get(&func)
.unwrap()
.instructions
.borrow()[pc]
.clone()
} else {
ctx.vm.bytecode.global.borrow()[pc].clone()
}
);
}
pub fn run(ctx: &mut TulispContext, bytecode: Bytecode) -> Result<TulispObject, Error> {
let labels = locate_labels(&bytecode);
ctx.vm.labels.extend(labels);
ctx.vm.bytecode.import_functions(&bytecode);
ctx.vm.bytecode.global = bytecode.global;
ctx.vm.bytecode.global_trace_ranges = bytecode.global_trace_ranges;
let global_program = ctx.vm.bytecode.global.clone();
let global_ranges = ctx.vm.bytecode.global_trace_ranges.clone();
run_impl(ctx, &global_program, global_ranges.as_slice(), 0)?;
Ok(ctx.vm.stack.pop().unwrap_or_else(TulispObject::nil))
}
pub(crate) fn run_lambda(
ctx: &mut TulispContext,
compiled: &CompiledDefun,
args: &[TulispObject],
) -> Result<TulispObject, Error> {
register_compiled_labels(ctx, compiled);
let required = compiled.params.required.len();
let optional = compiled.params.optional.len();
let has_rest = compiled.params.rest.is_some();
if args.len() < required {
return Err(Error::missing_argument("Too few arguments".to_string()));
}
if !has_rest && args.len() > required + optional {
return Err(Error::invalid_argument("Too many arguments".to_string()));
}
let left_args = args.len() - required;
let (optional_count, rest_count) = if left_args > optional {
(optional, left_args - optional)
} else {
(left_args, 0)
};
for a in args {
ctx.vm.stack.push(a.clone());
}
let mut current = compiled.clone();
let mut current_optional = optional_count;
let mut current_rest = rest_count;
loop {
let params = init_defun_args(ctx, ¤t.params, ¤t_optional, ¤t_rest)?;
let tail = run_function(
ctx,
¤t.instructions,
current.trace_ranges.as_slice(),
1,
)?;
drop(params);
match tail {
Some(info) => {
current = info.function;
current_optional = info.optional_count;
current_rest = info.rest_count;
}
None => break,
}
}
Ok(ctx.vm.stack.pop().unwrap())
}
fn run_impl(
ctx: &mut TulispContext,
program: &SharedMut<Vec<Instruction>>,
trace_ranges: &[TraceRange],
recursion_depth: u32,
) -> Result<Option<TailCallInfo>, Error> {
if ctx.eval_depth >= ctx.max_eval_depth {
return Err(Error::lisp_error(format!(
"Lisp nesting exceeds max-eval-depth ({})",
ctx.max_eval_depth
)));
}
ctx.eval_depth += 1;
let mut pc: usize = 0;
let result = run_impl_inner(ctx, program, &mut pc, recursion_depth);
ctx.eval_depth -= 1;
match result {
Ok(v) => Ok(v),
Err(mut e) => {
for range in trace_ranges.iter() {
if range.start_pc <= pc && pc < range.end_pc {
e = e.with_trace(range.form.clone());
}
}
Err(e)
}
}
}
fn run_impl_inner(
ctx: &mut TulispContext,
program: &SharedMut<Vec<Instruction>>,
pc_out: &mut usize,
recursion_depth: u32,
) -> Result<Option<TailCallInfo>, Error> {
let mut pc: usize = 0;
let program_size = program.borrow().len();
let mut instr_ref = program.borrow_mut();
let mut active = ActiveScopes::new();
while pc < program_size {
*pc_out = pc;
let instr = &mut instr_ref[pc];
match instr {
Instruction::Push(obj) => ctx.vm.stack.push(obj.clone()),
Instruction::Pop => {
ctx.vm.stack.pop();
}
Instruction::BinaryOp(op) => {
let [ref b, ref a] = ctx.vm.stack[(ctx.vm.stack.len() - 2)..] else {
unreachable!()
};
use crate::bytecode::instruction::BinaryOp;
let vv = match op {
BinaryOp::Add => binary_op_checked(a, b, Number::checked_add)?,
BinaryOp::Sub => binary_op_checked(a, b, Number::checked_sub)?,
BinaryOp::Mul => binary_op_checked(a, b, Number::checked_mul)?,
BinaryOp::Div => binary_op_checked(a, b, Number::checked_div)?,
};
ctx.vm.stack.truncate(ctx.vm.stack.len() - 2);
ctx.vm.stack.push(vv);
}
Instruction::LoadFile => {
let filename = ctx.vm.stack.pop().unwrap();
let filename = filename
.as_string()
.map_err(|err| err.with_trace(filename))?;
let full_path = if let Some(ref load_path) = ctx.load_path {
load_path.join(&filename)
} else {
std::path::PathBuf::from(&filename)
};
let full_path = full_path.to_str().ok_or_else(|| {
Error::invalid_argument(format!(
"load: Invalid path: {}",
full_path.to_string_lossy()
))
})?;
drop(instr_ref);
let result = vm_eval_file_inline(ctx, full_path)?;
instr_ref = program.borrow_mut();
ctx.vm.stack.push(result);
}
Instruction::PrintPop => {
let a = ctx.vm.stack.pop().unwrap();
println!("{}", a.fmt_string());
}
Instruction::Print => {
let a = ctx.vm.stack.last().unwrap();
println!("{}", a.fmt_string());
}
Instruction::JumpIfNil(pos) => {
let a = ctx.vm.stack.last().unwrap();
let cmp = a.null();
ctx.vm.stack.truncate(ctx.vm.stack.len() - 1);
if cmp {
jump_to_pos!(ctx, pc, pos);
continue;
}
}
Instruction::JumpIfNotNil(pos) => {
let a = ctx.vm.stack.last().unwrap();
let cmp = !a.null();
ctx.vm.stack.truncate(ctx.vm.stack.len() - 1);
if cmp {
jump_to_pos!(ctx, pc, pos);
continue;
}
}
Instruction::JumpIfNilElsePop(pos) => {
let a = ctx.vm.stack.last().unwrap();
if a.null() {
jump_to_pos!(ctx, pc, pos);
continue;
} else {
ctx.vm.stack.truncate(ctx.vm.stack.len() - 1);
}
}
Instruction::JumpIfNotNilElsePop(pos) => {
let a = ctx.vm.stack.last().unwrap();
if !a.null() {
jump_to_pos!(ctx, pc, pos);
continue;
} else {
ctx.vm.stack.truncate(ctx.vm.stack.len() - 1);
}
}
Instruction::JumpIfNeq(pos) => {
let minus2 = ctx.vm.stack.len() - 2;
let [ref b, ref a] = ctx.vm.stack[minus2..] else {
unreachable!()
};
let cmp = !a.eq(b);
ctx.vm.stack.truncate(minus2);
if cmp {
jump_to_pos!(ctx, pc, pos);
continue;
}
}
Instruction::JumpIfLt(pos) => {
let minus2 = ctx.vm.stack.len() - 2;
let [ref b, ref a] = ctx.vm.stack[minus2..] else {
unreachable!()
};
let cmp = compare_op(a, b, |a, b| a < b)?;
ctx.vm.stack.truncate(minus2);
if cmp {
jump_to_pos!(ctx, pc, pos);
continue;
}
}
Instruction::JumpIfLtEq(pos) => {
let minus2 = ctx.vm.stack.len() - 2;
let [ref b, ref a] = ctx.vm.stack[minus2..] else {
unreachable!()
};
let cmp = compare_op(a, b, |a, b| a <= b)?;
ctx.vm.stack.truncate(minus2);
if cmp {
jump_to_pos!(ctx, pc, pos);
continue;
}
}
Instruction::JumpIfGt(pos) => {
let minus2 = ctx.vm.stack.len() - 2;
let [ref b, ref a] = ctx.vm.stack[minus2..] else {
unreachable!()
};
let cmp = compare_op(a, b, |a, b| a > b)?;
ctx.vm.stack.truncate(minus2);
if cmp {
jump_to_pos!(ctx, pc, pos);
continue;
}
}
Instruction::JumpIfGtEq(pos) => {
let minus2 = ctx.vm.stack.len() - 2;
let [ref b, ref a] = ctx.vm.stack[minus2..] else {
unreachable!()
};
let cmp = compare_op(a, b, |a, b| a >= b)?;
ctx.vm.stack.truncate(minus2);
if cmp {
jump_to_pos!(ctx, pc, pos);
continue;
}
}
Instruction::Jump(pos) => {
jump_to_pos!(ctx, pc, pos);
continue;
}
Instruction::Equal => {
let a = ctx.vm.stack.pop().unwrap();
let b = ctx.vm.stack.pop().unwrap();
ctx.vm.stack.push(a.equal(&b).into());
}
Instruction::Eq => {
let a = ctx.vm.stack.pop().unwrap();
let b = ctx.vm.stack.pop().unwrap();
ctx.vm.stack.push(a.eq(&b).into());
}
Instruction::Lt => {
let a = ctx.vm.stack.pop().unwrap();
let b = ctx.vm.stack.pop().unwrap();
ctx.vm.stack.push(compare_op(&a, &b, |a, b| a < b)?.into());
}
Instruction::LtEq => {
let a = ctx.vm.stack.pop().unwrap();
let b = ctx.vm.stack.pop().unwrap();
ctx.vm.stack.push(compare_op(&a, &b, |a, b| a <= b)?.into());
}
Instruction::Gt => {
let a = ctx.vm.stack.pop().unwrap();
let b = ctx.vm.stack.pop().unwrap();
ctx.vm.stack.push(compare_op(&a, &b, |a, b| a > b)?.into());
}
Instruction::GtEq => {
let a = ctx.vm.stack.pop().unwrap();
let b = ctx.vm.stack.pop().unwrap();
ctx.vm.stack.push(compare_op(&a, &b, |a, b| a >= b)?.into());
}
Instruction::Set => {
let minus2 = ctx.vm.stack.len() - 2;
let [ref value, ref variable] = ctx.vm.stack[minus2..] else {
unreachable!()
};
variable.set(value.clone())?;
ctx.vm.stack.truncate(ctx.vm.stack.len() - 1);
}
Instruction::SetPop => {
let minus2 = ctx.vm.stack.len() - 2;
let [ref value, ref variable] = ctx.vm.stack[minus2..] else {
unreachable!()
};
variable.set(value.clone())?;
ctx.vm.stack.truncate(minus2);
}
Instruction::StorePop(obj) => {
let a = ctx.vm.stack.pop().unwrap();
obj.set(a)?;
}
Instruction::Store(obj) => {
let a = ctx.vm.stack.last().unwrap();
obj.set(a.clone())?;
}
Instruction::Load(obj) => {
let a = obj.get().map_err(|e| e.with_trace(obj.clone()))?;
ctx.vm.stack.push(a);
}
Instruction::BeginScope(obj) => {
let a = ctx.vm.stack.last().unwrap();
obj.set_scope(a.clone())?;
active.enter(obj.clone());
ctx.vm.stack.truncate(ctx.vm.stack.len() - 1);
}
Instruction::EndScope(obj) => {
obj.unset()?;
active.exit(obj);
}
Instruction::Call {
name,
form,
function,
args_count,
optional_count,
rest_count,
} => {
if function.is_none() {
let addr = name.addr_as_usize();
if let Some(func) = ctx.vm.bytecode.functions.get(&addr) {
let func = func.clone();
if *args_count < func.params.required.len() {
return Err(Error::missing_argument("Too few arguments".to_string())
.with_trace(form.clone()));
}
if func.params.rest.is_none()
&& *args_count > func.params.required.len() + func.params.optional.len()
{
return Err(Error::invalid_argument("Too many arguments".to_string())
.with_trace(form.clone()));
}
let left_args = *args_count - func.params.required.len();
if left_args > func.params.optional.len() {
*rest_count = left_args - func.params.optional.len();
*optional_count = func.params.optional.len();
} else if left_args > 0 {
*optional_count = left_args
}
*function = Some(func);
} else {
let args_count = *args_count;
let split_at = ctx.vm.stack.len() - args_count;
let args: Vec<TulispObject> = ctx.vm.stack.drain(split_at..).collect();
let name = name.clone();
let form = form.clone();
drop(instr_ref);
let result = funcall_inline(ctx, &name, args, recursion_depth)
.map_err(|e| e.with_trace(form))?;
ctx.vm.stack.push(result);
instr_ref = program.borrow_mut();
pc += 1;
continue;
}
}
let mut current_function = function.as_ref().unwrap().clone();
let mut current_optional = *optional_count;
let mut current_rest = *rest_count;
let form = form.clone();
drop(instr_ref);
loop {
let params = init_defun_args(
ctx,
¤t_function.params,
¤t_optional,
¤t_rest,
)?;
let tail = run_function(
ctx,
¤t_function.instructions,
current_function.trace_ranges.as_slice(),
recursion_depth + 1,
)
.map_err(|e| e.with_trace(form.clone()))?;
drop(params);
match tail {
Some(info) => {
current_function = info.function;
current_optional = info.optional_count;
current_rest = info.rest_count;
}
None => break,
}
}
instr_ref = program.borrow_mut();
}
Instruction::TailCall {
name,
form,
function,
args_count,
optional_count,
rest_count,
} => {
if function.is_none() {
let addr = name.addr_as_usize();
let Some(func) = ctx.vm.bytecode.functions.get(&addr) else {
return Err(Error::new(
crate::ErrorKind::Undefined,
format!("undefined function: {}", name),
)
.with_trace(form.clone()));
};
let func = func.clone();
if *args_count < func.params.required.len() {
return Err(Error::missing_argument("Too few arguments".to_string())
.with_trace(form.clone()));
}
if func.params.rest.is_none()
&& *args_count > func.params.required.len() + func.params.optional.len()
{
return Err(Error::invalid_argument("Too many arguments".to_string())
.with_trace(form.clone()));
}
let left_args = *args_count - func.params.required.len();
if left_args > func.params.optional.len() {
*rest_count = left_args - func.params.optional.len();
*optional_count = func.params.optional.len();
} else if left_args > 0 {
*optional_count = left_args
}
*function = Some(func);
}
let info = TailCallInfo {
function: function.as_ref().unwrap().clone(),
optional_count: *optional_count,
rest_count: *rest_count,
};
return Ok(Some(info));
}
Instruction::Ret => return Ok(None),
Instruction::MakeLambda(template) => {
let closure = make_lambda_from_template(ctx, template)?;
register_lambda_labels(ctx, &closure);
ctx.vm.stack.push(closure);
}
Instruction::Funcall { args_count } => {
let args_count = *args_count;
let split_at = ctx.vm.stack.len() - args_count;
let args: Vec<TulispObject> = ctx.vm.stack.drain(split_at..).collect();
let func = ctx.vm.stack.pop().unwrap();
drop(instr_ref);
let result = funcall_inline(ctx, &func, args, recursion_depth)?;
ctx.vm.stack.push(result);
instr_ref = program.borrow_mut();
}
Instruction::Apply { args_count } => {
let args_count = *args_count;
let final_list = ctx.vm.stack.pop().unwrap();
if !final_list.listp() {
return Err(Error::type_mismatch(format!(
"apply: last argument must be a list, got: {final_list}"
)));
}
let split_at = ctx.vm.stack.len() - args_count;
let mut args: Vec<TulispObject> = ctx.vm.stack.drain(split_at..).collect();
let func = ctx.vm.stack.pop().unwrap();
let mut slow = final_list.clone();
let mut fast = final_list.clone();
loop {
for _ in 0..2 {
if !fast.consp() {
break;
}
args.push(fast.car()?);
fast = fast.cdr()?;
}
if !fast.consp() {
if !fast.null() {
return Err(Error::type_mismatch(format!(
"apply: last argument must be a proper list, got non-nil tail: {fast}"
)));
}
break;
}
slow = slow.cdr()?;
if slow.eq_ptr(&fast) {
return Err(Error::out_of_range(
"apply: last argument is a circular list".to_string(),
));
}
}
drop(instr_ref);
let result = funcall_inline(ctx, &func, args, recursion_depth)?;
ctx.vm.stack.push(result);
instr_ref = program.borrow_mut();
}
Instruction::RustCall {
form,
func,
keep_result,
..
} => {
let args = ctx.vm.stack.pop().unwrap();
let result = func(ctx, &args).map_err(|e| e.with_trace(form.clone()))?;
if *keep_result {
ctx.vm.stack.push(result);
}
}
Instruction::RustCallTyped {
form,
call,
args_count,
keep_result,
..
} => {
let args_count = *args_count;
let split_at = ctx.vm.stack.len() - args_count;
let args: Vec<TulispObject> = ctx.vm.stack.drain(split_at..).collect();
let result = call(ctx, &args).map_err(|e| e.with_trace(form.clone()))?;
if *keep_result {
ctx.vm.stack.push(result);
}
}
Instruction::PushTrace(_) | Instruction::PopTrace => {
debug_assert!(
false,
"trace marker reached interpreter; strip_trace_markers should have lifted it",
);
}
Instruction::Label(_) => {}
Instruction::Cons => {
let b = ctx.vm.stack.pop().unwrap();
let a = ctx.vm.stack.pop().unwrap();
ctx.vm.stack.push(TulispObject::cons(a, b));
}
Instruction::List(len) => {
let mut list = TulispObject::nil();
for _ in 0..*len {
let a = ctx.vm.stack.pop().unwrap();
list = TulispObject::cons(a, list);
}
ctx.vm.stack.push(list);
}
Instruction::Append(len) => {
let mut iter = ctx.vm.stack.drain(ctx.vm.stack.len() - *len..);
let result = if let Some(last) = iter.next_back() {
let last: TulispObject = last;
let mut builder = crate::cons::ListBuilder::new();
for arg in iter.by_ref() {
let arg: TulispObject = arg;
if !arg.listp() {
return Err(Error::type_mismatch(format!(
"append: expected list, got: {arg}"
)));
}
for elem in arg.base_iter() {
builder.push(elem);
}
}
builder.build_with_tail(last)
} else {
TulispObject::nil()
};
drop(iter);
ctx.vm.stack.push(result);
}
Instruction::Cxr(cxr) => {
let a: TulispObject = ctx.vm.stack.pop().unwrap();
use crate::bytecode::instruction::Cxr;
let result = match cxr {
Cxr::Car => a.car()?,
Cxr::Cdr => a.cdr()?,
Cxr::Caar => a.caar()?,
Cxr::Cadr => a.cadr()?,
Cxr::Cdar => a.cdar()?,
Cxr::Cddr => a.cddr()?,
Cxr::Caaar => a.caaar()?,
Cxr::Caadr => a.caadr()?,
Cxr::Cadar => a.cadar()?,
Cxr::Caddr => a.caddr()?,
Cxr::Cdaar => a.cdaar()?,
Cxr::Cdadr => a.cdadr()?,
Cxr::Cddar => a.cddar()?,
Cxr::Cdddr => a.cdddr()?,
Cxr::Caaaar => a.caaaar()?,
Cxr::Caaadr => a.caaadr()?,
Cxr::Caadar => a.caadar()?,
Cxr::Caaddr => a.caaddr()?,
Cxr::Cadaar => a.cadaar()?,
Cxr::Cadadr => a.cadadr()?,
Cxr::Caddar => a.caddar()?,
Cxr::Cadddr => a.cadddr()?,
Cxr::Cdaaar => a.cdaaar()?,
Cxr::Cdaadr => a.cdaadr()?,
Cxr::Cdadar => a.cdadar()?,
Cxr::Cdaddr => a.cdaddr()?,
Cxr::Cddaar => a.cddaar()?,
Cxr::Cddadr => a.cddadr()?,
Cxr::Cdddar => a.cdddar()?,
Cxr::Cddddr => a.cddddr()?,
};
ctx.vm.stack.push(result);
}
Instruction::PlistGet => {
let [ref key, ref plist] = ctx.vm.stack[(ctx.vm.stack.len() - 2)..] else {
unreachable!()
};
let value = plist::plist_get(plist, key)?;
ctx.vm.stack.truncate(ctx.vm.stack.len() - 2);
ctx.vm.stack.push(value);
}
Instruction::Null => {
let a = ctx.vm.stack.last().unwrap().null();
*ctx.vm.stack.last_mut().unwrap() = a.into();
}
Instruction::Quote => {
let a = ctx.vm.stack.pop().unwrap();
ctx.vm
.stack
.push(TulispValue::Quote { value: a }.into_ref(None));
}
Instruction::WrapBackquote => {
let a = ctx.vm.stack.pop().unwrap();
ctx.vm
.stack
.push(TulispValue::Backquote { value: a }.into_ref(None));
}
Instruction::WrapUnquote => {
let a = ctx.vm.stack.pop().unwrap();
ctx.vm
.stack
.push(TulispValue::Unquote { value: a }.into_ref(None));
}
Instruction::WrapSplice => {
let a = ctx.vm.stack.pop().unwrap();
ctx.vm
.stack
.push(TulispValue::Splice { value: a }.into_ref(None));
}
}
pc += 1;
}
Ok(None)
}
fn init_defun_args(
ctx: &mut TulispContext,
params: &VMDefunParams,
optional_count: &usize,
rest_count: &usize,
) -> Result<SetParams, Error> {
let mut set_params = SetParams::new();
if let Some(rest) = ¶ms.rest {
let mut rest_value = TulispObject::nil();
for _ in 0..*rest_count {
rest_value = TulispObject::cons(ctx.vm.stack.pop().unwrap(), rest_value);
}
rest.set_scope(rest_value)?;
set_params.push(rest.clone());
}
for (ii, arg) in params.optional.iter().enumerate().rev() {
let val = if ii >= *optional_count {
TulispObject::nil()
} else {
ctx.vm.stack.pop().unwrap()
};
arg.set_scope(val)?;
set_params.push(arg.clone());
}
for arg in params.required.iter().rev() {
arg.set_scope(ctx.vm.stack.pop().unwrap())?;
set_params.push(arg.clone());
}
Ok(set_params)
}
fn run_function(
ctx: &mut TulispContext,
instructions: &SharedMut<Vec<Instruction>>,
trace_ranges: &[TraceRange],
recursion_depth: u32,
) -> Result<Option<TailCallInfo>, Error> {
run_impl(ctx, instructions, trace_ranges, recursion_depth)
}
fn funcall_inline(
ctx: &mut TulispContext,
func: &TulispObject,
args: Vec<TulispObject>,
recursion_depth: u32,
) -> Result<TulispObject, Error> {
if func.eq(&ctx.keywords.funcall) && !args.is_empty() {
let mut args = args;
let inner_func = args.remove(0);
return funcall_inline(ctx, &inner_func, args, recursion_depth);
}
let resolved = if (func.symbolp() && !func.keywordp()) || func.consp() {
ctx.eval(func)?
} else {
func.clone()
};
let inner = resolved.inner_ref();
match &inner.0 {
TulispValue::CompiledDefun { value } => {
let cd = value.clone();
drop(inner);
run_lambda_with(ctx, &cd, args, recursion_depth)
}
TulispValue::Defun { call, arity } => {
let call = call.clone();
let arity = arity.clone();
drop(inner);
if args.len() < arity.required {
return Err(Error::missing_argument("Too few arguments".to_string()));
}
if !arity.has_rest && args.len() > arity.required + arity.optional {
return Err(Error::invalid_argument("Too many arguments".to_string()));
}
call(ctx, &args)
}
TulispValue::Lambda { .. } | TulispValue::Func(_) => {
drop(inner);
let list = TulispObject::nil();
for a in args {
list.push(TulispValue::Quote { value: a }.into_ref(None))?;
}
crate::eval::funcall::<crate::eval::Eval>(ctx, &resolved, &list)
}
_ => Err(Error::undefined(format!("function is void: {}", resolved))),
}
}
fn run_lambda_with(
ctx: &mut TulispContext,
compiled: &CompiledDefun,
args: Vec<TulispObject>,
recursion_depth: u32,
) -> Result<TulispObject, Error> {
register_compiled_labels(ctx, compiled);
let required = compiled.params.required.len();
let optional = compiled.params.optional.len();
let has_rest = compiled.params.rest.is_some();
if args.len() < required {
return Err(Error::missing_argument("Too few arguments".to_string()));
}
if !has_rest && args.len() > required + optional {
return Err(Error::invalid_argument("Too many arguments".to_string()));
}
let left_args = args.len() - required;
let (optional_count, rest_count) = if left_args > optional {
(optional, left_args - optional)
} else {
(left_args, 0)
};
for a in args {
ctx.vm.stack.push(a);
}
let mut current = compiled.clone();
let mut current_optional = optional_count;
let mut current_rest = rest_count;
loop {
let params = init_defun_args(ctx, ¤t.params, ¤t_optional, ¤t_rest)?;
let tail = run_function(
ctx,
¤t.instructions,
current.trace_ranges.as_slice(),
recursion_depth + 1,
)?;
drop(params);
match tail {
Some(info) => {
current = info.function;
current_optional = info.optional_count;
current_rest = info.rest_count;
}
None => break,
}
}
Ok(ctx.vm.stack.pop().unwrap())
}
fn vm_eval_file_inline(ctx: &mut TulispContext, path: &str) -> Result<TulispObject, Error> {
let ast = ctx.parse_file(path)?;
let bytecode = crate::bytecode::compile(ctx, &ast)?;
let labels = locate_labels(&bytecode);
ctx.vm.labels.extend(labels);
ctx.vm.bytecode.import_functions(&bytecode);
let sub_global = bytecode.global.clone();
let sub_ranges = bytecode.global_trace_ranges.clone();
run_impl(ctx, &sub_global, sub_ranges.as_slice(), 1)?;
Ok(ctx.vm.stack.pop().unwrap_or_else(TulispObject::nil))
}
fn register_lambda_labels(ctx: &mut TulispContext, closure: &TulispObject) {
let inner = closure.inner_ref();
let TulispValue::CompiledDefun { value } = &inner.0 else {
return;
};
let instructions = value.instructions.clone();
drop(inner);
let borrow = instructions.borrow();
for (i, instr) in borrow.iter().enumerate() {
if let Instruction::Label(name) = instr {
ctx.vm.labels.insert(name.addr_as_usize(), i + 1);
}
}
}
fn register_compiled_labels(ctx: &mut TulispContext, compiled: &CompiledDefun) {
let borrow = compiled.instructions.borrow();
for (i, instr) in borrow.iter().enumerate() {
if let Instruction::Label(name) = instr {
ctx.vm.labels.insert(name.addr_as_usize(), i + 1);
}
}
}
fn placeholder_symbol(obj: &TulispObject) -> TulispObject {
let inner = obj.inner_ref();
if let TulispValue::LexicalBinding { binding } = &inner.0 {
let s = binding.symbol().clone();
drop(inner);
s
} else {
drop(inner);
obj.clone()
}
}
fn make_lambda_from_template(
ctx: &mut TulispContext,
template: &LambdaTemplate,
) -> Result<TulispObject, Error> {
let allocator = ctx.lex_allocator.clone();
let mut mapping: HashMap<usize, TulispObject> =
HashMap::with_capacity(template.param_placeholders.len() + template.free_vars.len());
for (orig, placeholder) in &template.free_vars {
let slot = {
let inner = orig.inner_ref();
match &inner.0 {
TulispValue::LexicalBinding { binding } => binding.current_slot(),
_ => None,
}
};
let replacement = if let Some(slot) = slot {
TulispObject::lexical_binding_captured(allocator.clone(), orig.clone(), slot)
} else {
orig.clone()
};
mapping.insert(placeholder.addr_as_usize(), replacement);
}
for placeholder in &template.param_placeholders {
let sym = placeholder_symbol(placeholder);
let fresh = TulispObject::lexical_binding(allocator.clone(), sym);
mapping.insert(placeholder.addr_as_usize(), fresh);
}
let rewrite = |obj: &mut TulispObject| {
if let Some(replacement) = mapping.get(&obj.addr_as_usize()) {
*obj = replacement.clone();
}
};
let mut instructions = template.instructions.clone();
for insn in instructions.iter_mut() {
rewrite_instruction(insn, &mapping, &rewrite);
}
let rewrite_obj = |obj: &TulispObject| -> TulispObject {
mapping
.get(&obj.addr_as_usize())
.cloned()
.unwrap_or_else(|| obj.clone())
};
let params = VMDefunParams {
required: template.params.required.iter().map(&rewrite_obj).collect(),
optional: template.params.optional.iter().map(&rewrite_obj).collect(),
rest: template.params.rest.as_ref().map(&rewrite_obj),
};
let cd = CompiledDefun {
name: TulispObject::nil(),
instructions: SharedMut::new(instructions),
trace_ranges: crate::object::wrappers::generic::Shared::new_sized(
template.trace_ranges.clone(),
),
params,
};
Ok(TulispValue::CompiledDefun { value: cd }.into_ref(None))
}
fn rewrite_instruction(
insn: &mut Instruction,
mapping: &HashMap<usize, TulispObject>,
rewrite: &impl Fn(&mut TulispObject),
) {
match insn {
Instruction::Load(o)
| Instruction::Store(o)
| Instruction::StorePop(o)
| Instruction::BeginScope(o)
| Instruction::EndScope(o) => rewrite(o),
Instruction::Push(o)
if ast_contains_placeholder(o, mapping) => {
*o = rewrite_ast(o, mapping);
}
Instruction::MakeLambda(template) => {
let rebuilt = rewrite_template(template, mapping);
*template = crate::object::wrappers::generic::Shared::new_sized(rebuilt);
}
_ => {}
}
}
fn ast_contains_placeholder(obj: &TulispObject, mapping: &HashMap<usize, TulispObject>) -> bool {
if mapping.contains_key(&obj.addr_as_usize()) {
return true;
}
if obj.consp() {
let mut cur = obj.clone();
while cur.consp() {
let Ok(car) = cur.car() else { break };
if ast_contains_placeholder(&car, mapping) {
return true;
}
let Ok(next) = cur.cdr() else { break };
if !next.consp() {
if mapping.contains_key(&next.addr_as_usize()) {
return true;
}
break;
}
cur = next;
}
}
false
}
fn rewrite_ast(obj: &TulispObject, mapping: &HashMap<usize, TulispObject>) -> TulispObject {
if let Some(replacement) = mapping.get(&obj.addr_as_usize()) {
return replacement.clone();
}
if obj.consp() {
let span = obj.span();
let mut builder = crate::cons::ListBuilder::new();
let mut cur = obj.clone();
loop {
let Ok(car) = cur.car() else { break };
builder.push(rewrite_ast(&car, mapping));
let Ok(next) = cur.cdr() else { break };
if next.null() {
break;
}
if !next.consp() {
let tail = mapping
.get(&next.addr_as_usize())
.cloned()
.unwrap_or_else(|| next.clone());
let _ = builder.append(tail);
break;
}
cur = next;
}
return builder.build().with_span(span);
}
obj.clone()
}
fn rewrite_template(
template: &LambdaTemplate,
mapping: &HashMap<usize, TulispObject>,
) -> LambdaTemplate {
let rewrite = |obj: &mut TulispObject| {
if let Some(replacement) = mapping.get(&obj.addr_as_usize()) {
*obj = replacement.clone();
}
};
let new_free_vars: Vec<(TulispObject, TulispObject)> = template
.free_vars
.iter()
.map(|(orig, ph)| {
let new_orig = mapping
.get(&orig.addr_as_usize())
.cloned()
.unwrap_or_else(|| orig.clone());
(new_orig, ph.clone())
})
.collect();
let mut new_instructions = template.instructions.clone();
for insn in new_instructions.iter_mut() {
rewrite_instruction(insn, mapping, &rewrite);
}
LambdaTemplate {
instructions: new_instructions,
trace_ranges: template.trace_ranges.clone(),
param_placeholders: template.param_placeholders.clone(),
params: template.params.clone(),
free_vars: new_free_vars,
}
}