use winnow::{
ModalResult, Parser,
combinator::{alt, cut_err, delimited, fail, preceded, separated},
};
use crate::{
StatefulInput,
binding::Binding,
context::Context,
expr::Expr,
ident::Ident,
macros::{exp_char, exp_desc, label},
state::{EvalState, FmtState},
utils::delimited_multispace0,
value::Value,
};
#[derive(Debug, Clone)]
pub struct Defined {
ctx_idx: usize,
body: Box<Expr>,
}
impl Defined {
pub(crate) fn parse(input: &mut StatefulInput) -> ModalResult<Self> {
let current_ctx_idx = input.state.active_ctx_idx();
let ctx_idx = input.state.avail_ctx_idx();
input.state.increment_avail_ctx_idx();
input.state.set_active_ctx(ctx_idx);
let parse_result = (
delimited(
'|',
alt((
delimited_multispace0(separated(
1..,
Ident::parse_ident,
delimited_multispace0(','),
)),
cut_err(fail).context(exp_desc!("one or more parameters")),
)),
alt((
preceded(
delimited_multispace0(','),
cut_err(']')
.context(exp_desc!("an expression"))
.context(exp_char!(']')),
),
cut_err('|').context(exp_char!(',')).context(exp_char!('|')),
)),
),
Expr::require_parse.map(Box::new),
)
.context(label!("function declaration"))
.parse_next(input);
input.state.set_active_ctx(current_ctx_idx);
let (params, body): (Vec<Ident>, Box<Expr>) = parse_result.inspect_err(|_| {
input.state.decrement_avail_ctx_idx();
})?;
let ctx = Context::from_iter(
Some(current_ctx_idx),
params.into_iter().map(|p| (p, Binding::default())),
);
input.state.place_ctx(ctx_idx, ctx);
Ok(Self { ctx_idx, body })
}
pub(crate) fn evaluate(self, state: &mut EvalState, args: Vec<Expr>) -> Value {
if args.len() != state[self.ctx_idx].len() {
return Value::Null;
}
state[self.ctx_idx].assign_from_iter(args);
let current_ctx_idx = state.active_ctx_idx();
state.set_active_ctx(self.ctx_idx);
let value = self.body.to_owned().evaluate(state);
state.set_active_ctx(current_ctx_idx);
state[self.ctx_idx].reassign_default_expr();
value
}
pub(crate) fn format<W: std::fmt::Write>(
&self,
writer: &mut W,
state: FmtState,
) -> std::fmt::Result {
write!(writer, "|")?;
let mut params_iter = state[self.ctx_idx].keys().peekable();
while let Some(param) = params_iter.next() {
param.format(writer, state)?;
if params_iter.peek().is_some() {
write!(writer, ",")?;
if state.pretty() {
write!(writer, " ")?;
}
}
}
write!(writer, "|")?;
if state.pretty() {
write!(writer, " ")?;
}
self.body.format(writer, state)
}
}