use crate::eval::EvalError;
use crate::eval::Evaluable;
use ergotree_ir::mir::value::Value;
use super::EvalFn;
pub(crate) static MAP_EVAL_FN: EvalFn = |env, ctx, obj, args| {
let input_v = obj;
let lambda_v = args
.get(0)
.cloned()
.ok_or_else(|| EvalError::NotFound("map: eval is missing first arg".to_string()))?;
let input_v_clone = input_v.clone();
let lambda = match &lambda_v {
Value::Lambda(l) => Ok(l),
_ => Err(EvalError::UnexpectedValue(format!(
"expected lambda to be Value::FuncValue got: {0:?}",
input_v_clone
))),
}?;
let mut lambda_call = |arg: Value| {
let func_arg = lambda.args.first().ok_or_else(|| {
EvalError::NotFound("map: lambda has empty arguments list".to_string())
})?;
let env1 = env.clone().extend(func_arg.idx, arg);
lambda.body.eval(&env1, ctx)
};
let normalized_input_val: Option<Value> = match input_v {
Value::Opt(opt) => Ok(*opt),
_ => Err(EvalError::UnexpectedValue(format!(
"expected map input to be Value::Opt, got: {0:?}",
input_v
))),
}?;
match normalized_input_val {
Some(t) => Ok(Value::Opt(Box::new(lambda_call(t)?.into()))),
_ => Ok(Value::Opt(Box::new(None))),
}
};
pub(crate) static FILTER_EVAL_FN: EvalFn = |env, ctx, obj, args| {
let input_v = obj;
let lambda_v = args
.get(0)
.cloned()
.ok_or_else(|| EvalError::NotFound("filter: eval is missing first arg".to_string()))?;
let input_v_clone = input_v.clone();
let lambda = match &lambda_v {
Value::Lambda(l) => Ok(l),
_ => Err(EvalError::UnexpectedValue(format!(
"expected lambda to be Value::FuncValue got: {0:?}",
input_v_clone
))),
}?;
let mut predicate_call = |arg: Value| {
let func_arg = lambda.args.first().ok_or_else(|| {
EvalError::NotFound("filter: lambda has empty arguments list".to_string())
})?;
let env1 = env.clone().extend(func_arg.idx, arg);
lambda.body.eval(&env1, ctx)
};
let normalized_input_val: Option<Value> = match input_v {
Value::Opt(opt) => Ok(*opt),
_ => Err(EvalError::UnexpectedValue(format!(
"expected filter input to be Value::Opt, got: {0:?}",
input_v
))),
}?;
match normalized_input_val {
Some(val) => match predicate_call(val.clone())? {
Value::Boolean(predicate_res) => match predicate_res {
true => Ok(Value::Opt(Box::new(Some(val)))),
false => Ok(Value::Opt(Box::new(None))),
},
_ => Err(EvalError::UnexpectedValue(format!(
"expected filter predicate result to be boolean, got: {0:?}",
lambda.body.tpe()
))),
},
None => Ok(Value::Opt(Box::new(None))),
}
};
#[allow(clippy::unwrap_used)]
#[cfg(test)]
#[cfg(feature = "arbitrary")]
mod tests {
use ergotree_ir::mir::bin_op::RelationOp;
use ergotree_ir::mir::bin_op::{ArithOp, BinOp};
use ergotree_ir::mir::constant::Constant;
use ergotree_ir::mir::expr::Expr;
use ergotree_ir::mir::func_value::FuncArg;
use ergotree_ir::mir::func_value::FuncValue;
use ergotree_ir::mir::method_call::MethodCall;
use ergotree_ir::mir::val_use::ValUse;
use ergotree_ir::types::soption;
use ergotree_ir::types::stype::SType;
use ergotree_ir::types::stype_param::STypeVar;
use crate::eval::tests::eval_out_wo_ctx;
use ergotree_ir::mir::value::Value;
#[test]
fn eval_map_some() {
let opt_const: Constant = Some(1i64).into();
let body: Expr = BinOp {
kind: RelationOp::Gt.into(),
left: Box::new(
ValUse {
val_id: 1.into(),
tpe: SType::SBox,
}
.into(),
),
right: Box::new(Expr::Const(0i64.into())),
}
.into();
let expr: Expr = MethodCall::new(
opt_const.into(),
soption::MAP_METHOD.clone().with_concrete_types(
&[
(STypeVar::iv(), SType::SLong),
(STypeVar::ov(), SType::SBoolean),
]
.iter()
.cloned()
.collect(),
),
vec![FuncValue::new(
vec![FuncArg {
idx: 1.into(),
tpe: SType::SLong,
}],
body,
)
.into()],
)
.unwrap()
.into();
let res = eval_out_wo_ctx::<Value>(&expr);
assert_eq!(
res,
Value::Opt(Box::new(Option::Some(Value::Boolean(true))))
);
}
#[test]
fn eval_map_none() {
let typed_none: Option<i64> = None;
let opt_const: Constant = typed_none.into();
let body: Expr = BinOp {
kind: RelationOp::Gt.into(),
left: Box::new(
ValUse {
val_id: 1.into(),
tpe: SType::SBox,
}
.into(),
),
right: Box::new(Expr::Const(0i64.into())),
}
.into();
let expr: Expr = MethodCall::new(
opt_const.into(),
soption::MAP_METHOD.clone().with_concrete_types(
&[
(STypeVar::iv(), SType::SLong),
(STypeVar::ov(), SType::SBoolean),
]
.iter()
.cloned()
.collect(),
),
vec![FuncValue::new(
vec![FuncArg {
idx: 1.into(),
tpe: SType::SLong,
}],
body,
)
.into()],
)
.unwrap()
.into();
let res = eval_out_wo_ctx::<Value>(&expr);
assert_eq!(res, Value::Opt(Box::new(None)));
}
#[test]
fn eval_filter_some_true() {
let opt_const: Constant = Some(1i64).into();
let body: Expr = BinOp {
kind: RelationOp::Gt.into(),
left: Box::new(
ValUse {
val_id: 1.into(),
tpe: SType::SBox,
}
.into(),
),
right: Box::new(Expr::Const(0i64.into())),
}
.into();
let expr: Expr = MethodCall::new(
opt_const.into(),
soption::FILTER_METHOD
.clone()
.with_concrete_types(&[(STypeVar::iv(), SType::SLong)].iter().cloned().collect()),
vec![FuncValue::new(
vec![FuncArg {
idx: 1.into(),
tpe: SType::SLong,
}],
body,
)
.into()],
)
.unwrap()
.into();
let res = eval_out_wo_ctx::<Value>(&expr);
assert_eq!(res, Value::Opt(Box::new(Option::Some(Value::Long(1)))));
}
#[test]
fn eval_filter_some_false() {
let opt_const: Constant = Some(1i64).into();
let body: Expr = BinOp {
kind: RelationOp::Gt.into(),
left: Box::new(
ValUse {
val_id: 1.into(),
tpe: SType::SBox,
}
.into(),
),
right: Box::new(Expr::Const(10i64.into())),
}
.into();
let expr: Expr = MethodCall::new(
opt_const.into(),
soption::FILTER_METHOD
.clone()
.with_concrete_types(&[(STypeVar::iv(), SType::SLong)].iter().cloned().collect()),
vec![FuncValue::new(
vec![FuncArg {
idx: 1.into(),
tpe: SType::SLong,
}],
body,
)
.into()],
)
.unwrap()
.into();
let res = eval_out_wo_ctx::<Value>(&expr);
assert_eq!(res, Value::Opt(Box::new(Option::None)));
}
#[test]
fn eval_filter_none() {
let typed_none: Option<i64> = None;
let opt_const: Constant = typed_none.into();
let body: Expr = BinOp {
kind: RelationOp::Gt.into(),
left: Box::new(
ValUse {
val_id: 1.into(),
tpe: SType::SBox,
}
.into(),
),
right: Box::new(Expr::Const(0i64.into())),
}
.into();
let expr: Expr = MethodCall::new(
opt_const.into(),
soption::FILTER_METHOD
.clone()
.with_concrete_types(&[(STypeVar::iv(), SType::SLong)].iter().cloned().collect()),
vec![FuncValue::new(
vec![FuncArg {
idx: 1.into(),
tpe: SType::SLong,
}],
body,
)
.into()],
)
.unwrap()
.into();
let res = eval_out_wo_ctx::<Value>(&expr);
assert_eq!(res, Value::Opt(Box::new(Option::None)));
}
#[test]
fn eval_filter_predicate_invalid_tpe() {
let opt_const: Constant = Some(1i64).into();
let body: Expr = BinOp {
kind: ArithOp::Plus.into(),
left: Box::new(
ValUse {
val_id: 1.into(),
tpe: SType::SBox,
}
.into(),
),
right: Box::new(Expr::Const(2i64.into())),
}
.into();
body.tpe();
assert!(MethodCall::new(
opt_const.into(),
soption::FILTER_METHOD
.clone()
.with_concrete_types(&[(STypeVar::iv(), SType::SLong)].iter().cloned().collect()),
vec![FuncValue::new(
vec![FuncArg {
idx: 1.into(),
tpe: SType::SLong,
}],
body,
)
.into()],
)
.is_err());
}
}