use crate::{
Error, ErrorKind, TulispContext, TulispObject,
bytecode::{
Instruction, LambdaTemplate,
compiler::{
VMDefunParams,
compiler::{compile_expr_keep_result, compile_progn_keep_result},
free_vars::classify_free_vars,
},
},
eval::substitute_lexical,
object::wrappers::generic::Shared,
};
pub(super) fn compile_fn_lambda(
ctx: &mut TulispContext,
name: &TulispObject,
args: &TulispObject,
) -> Result<Vec<Instruction>, Error> {
ctx.compile_2_arg_call(name, args, true, |ctx, params, body_head, body_rest| {
let body = if body_head.as_string().is_ok() {
body_rest.clone()
} else {
let out = TulispObject::nil();
out.push(body_head.clone())?;
for item in body_rest.base_iter() {
out.push(item)?;
}
out
};
let mut param_names: Vec<TulispObject> = Vec::new();
let mut vm_params = VMDefunParams {
required: Vec::new(),
optional: Vec::new(),
rest: None,
};
let mut seen_rest = false;
for p in params.base_iter() {
if p.eq(&ctx.keywords.amp_optional) {
if seen_rest {
return Err(Error::new(
ErrorKind::Undefined,
"optional after rest".to_string(),
)
.with_trace(p));
}
continue;
}
if p.eq(&ctx.keywords.amp_rest) {
if seen_rest {
return Err(
Error::new(ErrorKind::Undefined, "rest after rest".to_string())
.with_trace(p),
);
}
seen_rest = true;
continue;
}
param_names.push(p);
}
let free = classify_free_vars(&body, ¶m_names)?;
let mut param_placeholders: Vec<TulispObject> = Vec::with_capacity(param_names.len());
for name in ¶m_names {
let lex = TulispObject::lexical_binding(ctx.lex_allocator.clone(), name.clone());
param_placeholders.push(lex);
}
{
let mut cursor = 0usize;
let mut is_optional = false;
let mut is_rest = false;
for p in params.base_iter() {
if p.eq(&ctx.keywords.amp_optional) {
is_optional = true;
continue;
}
if p.eq(&ctx.keywords.amp_rest) {
is_optional = false;
is_rest = true;
continue;
}
let ph = param_placeholders[cursor].clone();
cursor += 1;
if is_rest {
if vm_params.rest.is_some() {
return Err(Error::new(
ErrorKind::Undefined,
"multiple rest arguments".to_string(),
)
.with_trace(p));
}
vm_params.rest = Some(ph);
} else if is_optional {
vm_params.optional.push(ph);
} else {
vm_params.required.push(ph);
}
}
}
let mut free_vars: Vec<(TulispObject, TulispObject)> = Vec::with_capacity(free.len());
for orig in free {
let symbol_for_ph = match &orig.inner_ref().0 {
crate::TulispValue::LexicalBinding { binding } => binding.symbol().clone(),
_ => orig.clone(),
};
let ph = TulispObject::lexical_binding(ctx.lex_allocator.clone(), symbol_for_ph);
free_vars.push((orig, ph));
}
let mut mappings: Vec<(TulispObject, TulispObject)> = Vec::new();
for (name, ph) in param_names.iter().zip(param_placeholders.iter()) {
mappings.push((name.clone(), ph.clone()));
}
for (orig, ph) in &free_vars {
mappings.push((orig.clone(), ph.clone()));
}
let body = substitute_lexical(body, &mappings)?;
let prev_scopes = std::mem::take(&mut ctx.compiler.as_mut().unwrap().active_let_scopes);
let body_result = compile_progn_keep_result(ctx, &body);
ctx.compiler.as_mut().unwrap().active_let_scopes = prev_scopes;
let mut instructions = body_result?;
instructions.push(Instruction::Ret);
let (instructions, trace_ranges) =
crate::bytecode::bytecode::strip_trace_markers(instructions);
let template = LambdaTemplate {
instructions,
trace_ranges,
param_placeholders,
params: vm_params,
free_vars,
};
let mut result = Vec::with_capacity(1);
if ctx.compiler.as_ref().unwrap().keep_result {
result.push(Instruction::MakeLambda(Shared::new_sized(template)));
}
Ok(result)
})
}
pub(super) fn compile_fn_funcall(
ctx: &mut TulispContext,
_name: &TulispObject,
args: &TulispObject,
) -> Result<Vec<Instruction>, Error> {
if !args.consp() {
return Err(Error::new(
ErrorKind::TypeMismatch,
"funcall requires at least 1 argument".to_string(),
));
}
let mut result = Vec::new();
let fn_expr = args.car()?;
result.append(&mut compile_expr_keep_result(ctx, &fn_expr)?);
let mut args_count = 0usize;
let mut rest = args.cdr()?;
while rest.consp() {
let arg = rest.car()?;
result.append(&mut compile_expr_keep_result(ctx, &arg)?);
args_count += 1;
rest = rest.cdr()?;
}
result.push(Instruction::Funcall { args_count });
if !ctx.compiler.as_ref().unwrap().keep_result {
result.push(Instruction::Pop);
}
Ok(result)
}
pub(super) fn compile_fn_apply(
ctx: &mut TulispContext,
_name: &TulispObject,
args: &TulispObject,
) -> Result<Vec<Instruction>, Error> {
if !args.consp() {
return Err(Error::new(
ErrorKind::MissingArgument,
"apply requires at least 2 arguments".to_string(),
));
}
let mut result = Vec::new();
let fn_expr = args.car()?;
result.append(&mut compile_expr_keep_result(ctx, &fn_expr)?);
let mut total_args = 0usize;
let mut rest = args.cdr()?;
while rest.consp() {
let arg = rest.car()?;
result.append(&mut compile_expr_keep_result(ctx, &arg)?);
total_args += 1;
rest = rest.cdr()?;
}
if total_args == 0 {
return Err(Error::new(
ErrorKind::MissingArgument,
"apply requires at least 2 arguments".to_string(),
));
}
let intermediate = total_args - 1;
result.push(Instruction::Apply {
args_count: intermediate,
});
if !ctx.compiler.as_ref().unwrap().keep_result {
result.push(Instruction::Pop);
}
Ok(result)
}