use crate::{
alloc::{vec, Vec},
error::AuxErrorInfo,
fns::extract_fn,
CallContext, ErrorKind, EvalResult, NativeFn, SpannedValue, Value,
};
#[derive(Debug, Clone, Copy, Default)]
pub struct If;
impl<T> NativeFn<T> for If {
fn evaluate<'a>(
&self,
mut args: Vec<SpannedValue<'a, T>>,
ctx: &mut CallContext<'_, 'a, T>,
) -> EvalResult<'a, T> {
ctx.check_args_count(&args, 3)?;
let else_val = args.pop().unwrap().extra;
let then_val = args.pop().unwrap().extra;
if let Value::Bool(condition) = &args[0].extra {
Ok(if *condition { then_val } else { else_val })
} else {
let err = ErrorKind::native("`if` requires first arg to be boolean");
Err(ctx
.call_site_error(err)
.with_span(&args[0], AuxErrorInfo::InvalidArg))
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Loop;
impl Loop {
const ITER_ERROR: &'static str =
"iteration function should return a 2-element tuple with first bool value";
}
impl<T: Clone> NativeFn<T> for Loop {
fn evaluate<'a>(
&self,
mut args: Vec<SpannedValue<'a, T>>,
ctx: &mut CallContext<'_, 'a, T>,
) -> EvalResult<'a, T> {
ctx.check_args_count(&args, 2)?;
let iter = args.pop().unwrap();
let iter = if let Value::Function(iter) = iter.extra {
iter
} else {
let err = ErrorKind::native("Second argument of `loop` should be an iterator function");
return Err(ctx
.call_site_error(err)
.with_span(&iter, AuxErrorInfo::InvalidArg));
};
let mut arg = args.pop().unwrap();
loop {
if let Value::Tuple(mut tuple) = iter.evaluate(vec![arg], ctx)? {
let (ret_or_next_arg, flag) = if tuple.len() == 2 {
(tuple.pop().unwrap(), tuple.pop().unwrap())
} else {
let err = ErrorKind::native(Self::ITER_ERROR);
break Err(ctx.call_site_error(err));
};
match (flag, ret_or_next_arg) {
(Value::Bool(false), ret) => break Ok(ret),
(Value::Bool(true), next_arg) => {
arg = ctx.apply_call_span(next_arg);
}
_ => {
let err = ErrorKind::native(Self::ITER_ERROR);
break Err(ctx.call_site_error(err));
}
}
} else {
let err = ErrorKind::native(Self::ITER_ERROR);
break Err(ctx.call_site_error(err));
}
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct While;
impl<T: Clone> NativeFn<T> for While {
fn evaluate<'a>(
&self,
mut args: Vec<SpannedValue<'a, T>>,
ctx: &mut CallContext<'_, 'a, T>,
) -> EvalResult<'a, T> {
ctx.check_args_count(&args, 3)?;
let step_fn = extract_fn(
ctx,
args.pop().unwrap(),
"`while` requires third arg to be a step function",
)?;
let condition_fn = extract_fn(
ctx,
args.pop().unwrap(),
"`while` requires second arg to be a condition function",
)?;
let mut state = args.pop().unwrap();
let state_span = state.copy_with_extra(());
loop {
let condition_value = condition_fn.evaluate(vec![state.clone()], ctx)?;
match condition_value {
Value::Bool(true) => {
let new_state = step_fn.evaluate(vec![state], ctx)?;
state = state_span.copy_with_extra(new_state);
}
Value::Bool(false) => break Ok(state.extra),
_ => {
let err =
ErrorKind::native("`while` requires condition function to return booleans");
return Err(ctx.call_site_error(err));
}
}
}
}
}