Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lazy default for Option.getOrElse/Coll.getOrElse #798

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 44 additions & 5 deletions ergotree-interpreter/src/eval/coll_by_index.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use ergotree_ir::ergo_tree::ErgoTreeVersion;
use ergotree_ir::mir::coll_by_index::ByIndex;
use ergotree_ir::mir::constant::TryExtractInto;
use ergotree_ir::mir::value::CollKind;
Expand All @@ -23,12 +24,16 @@ impl Evaluable for ByIndex {
input_v
))),
}?;
match self.default.clone() {
match self.default.as_ref() {
Some(default) => {
let default_v = default.eval(env, ctx)?;
Ok(normalized_input_vals
.get_val(index_v.try_extract_into::<i32>()? as usize)
.unwrap_or(default_v))
let mut default_v = || default.eval(env, ctx);
let val =
normalized_input_vals.get_val(index_v.try_extract_into::<i32>()? as usize);
if ctx.tree_version() >= ErgoTreeVersion::V3 {
val.map(Ok).unwrap_or_else(default_v)
} else {
Ok(val.unwrap_or(default_v()?))
}
}
None => normalized_input_vals
.get_val(index_v.clone().try_extract_into::<i32>()? as usize)
Expand All @@ -47,6 +52,10 @@ impl Evaluable for ByIndex {
#[cfg(test)]
mod tests {
use ergotree_ir::chain::ergo_box::ErgoBox;
use ergotree_ir::mir::bin_op::ArithOp;
use ergotree_ir::mir::bin_op::BinOp;
use ergotree_ir::mir::bin_op::BinOpKind;
use ergotree_ir::mir::constant::Constant;
use ergotree_ir::mir::expr::Expr;
use ergotree_ir::mir::global_vars::GlobalVars;
use ergotree_ir::reference::Ref;
Expand All @@ -55,6 +64,7 @@ mod tests {
use super::*;
use crate::eval::tests::eval_out;
use crate::eval::tests::eval_out_wo_ctx;
use crate::eval::tests::try_eval_out_with_version;
use ergotree_ir::chain::context::Context;

#[test]
Expand All @@ -80,4 +90,33 @@ mod tests {
.into();
assert_eq!(eval_out_wo_ctx::<i64>(&expr), 5);
}

#[test]
fn eval_lazy() {
let expr: Expr = ByIndex::new(
Expr::Const(vec![1i64, 2i64].into()),
Expr::Const(1i32.into()),
Some(Box::new(Expr::BinOp(
BinOp {
kind: BinOpKind::Arith(ArithOp::Divide),
left: Box::new(Constant::from(1i64).into()),
right: Box::new(Constant::from(0i64).into()),
}
.into(),
))),
)
.unwrap()
.into();
let ctx = force_any_val::<Context>();
for tree_version in 0u8..ErgoTreeVersion::V3.into() {
assert!(try_eval_out_with_version::<Value>(&expr, &ctx, tree_version, 3).is_err());
}
for tree_version in ErgoTreeVersion::V3.into()..=ErgoTreeVersion::MAX_SCRIPT_VERSION.into()
{
assert_eq!(
try_eval_out_with_version::<i64>(&expr, &ctx, tree_version, 3).unwrap(),
2i64
);
}
}
}
6 changes: 3 additions & 3 deletions ergotree-interpreter/src/eval/extract_reg_as.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use alloc::sync::Arc;
use core::convert::TryInto;

use alloc::boxed::Box;
use ergotree_ir::chain::ergo_box::ErgoBox;
use ergotree_ir::mir::constant::TryExtractInto;
use ergotree_ir::mir::extract_reg_as::ExtractRegisterAs;
Expand Down Expand Up @@ -35,13 +35,13 @@ impl Evaluable for ExtractRegisterAs {
})?;
match reg_val_opt {
Some(constant) if constant.tpe == *self.elem_tpe => {
Ok(Value::Opt(Box::new(Some(constant.v.into()))))
Ok(Value::Opt(Some(Arc::new(constant.v.into()))))
}
Some(constant) => Err(EvalError::UnexpectedValue(format!(
"Expected register {id} to be of type {}, got {}",
self.elem_tpe, constant.tpe
))),
None => Ok(Value::Opt(Box::new(None))),
None => Ok(Value::Opt(None)),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion ergotree-interpreter/src/eval/get_var.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::eval::Evaluable;
impl Evaluable for GetVar {
fn eval<'ctx>(&self, _env: &mut Env, ctx: &Context<'ctx>) -> Result<Value<'ctx>, EvalError> {
match ctx.extension.values.get(&self.var_id) {
None => Ok(Value::Opt(None.into())),
None => Ok(Value::Opt(None)),
Some(v) if v.tpe == self.var_tpe => Ok((Some(v.v.clone())).into()),
Some(v) => Err(TryExtractFromError(format!(
"GetVar: expected extension value id {} to have type {:?}, found {:?} in context extension map {}",
Expand Down
7 changes: 4 additions & 3 deletions ergotree-interpreter/src/eval/option_get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ impl Evaluable for OptionGet {
) -> Result<Value<'ctx>, EvalError> {
let v = self.input.eval(env, ctx)?;
match v {
Value::Opt(opt_v) => {
opt_v.ok_or_else(|| EvalError::NotFound("calling Option.get on None".to_string()))
}
Value::Opt(opt_v) => opt_v
.as_deref()
.ok_or_else(|| EvalError::NotFound("calling Option.get on None".to_string()))
.cloned(),
_ => Err(EvalError::UnexpectedExpr(format!(
"Don't know how to eval OptM: {0:?}",
self
Expand Down
48 changes: 45 additions & 3 deletions ergotree-interpreter/src/eval/option_get_or_else.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use ergotree_ir::ergo_tree::ErgoTreeVersion;
use ergotree_ir::mir::option_get_or_else::OptionGetOrElse;
use ergotree_ir::mir::value::Value;

Expand All @@ -13,9 +14,12 @@ impl Evaluable for OptionGetOrElse {
ctx: &Context<'ctx>,
) -> Result<Value<'ctx>, EvalError> {
let v = self.input.eval(env, ctx)?;
let default_v = self.default.eval(env, ctx)?;
let mut default_v = || self.default.eval(env, ctx);
match v {
Value::Opt(opt_v) => Ok(opt_v.unwrap_or(default_v)),
Value::Opt(opt_v) if ctx.tree_version() >= ErgoTreeVersion::V3 => {
opt_v.as_deref().cloned().map(Ok).unwrap_or_else(default_v)
}
Value::Opt(opt_v) => Ok(opt_v.as_deref().cloned().unwrap_or(default_v()?)),
_ => Err(EvalError::UnexpectedExpr(format!(
"Don't know how to eval OptM: {0:?}",
self
Expand All @@ -28,13 +32,16 @@ impl Evaluable for OptionGetOrElse {
#[cfg(test)]
mod tests {
use super::OptionGetOrElse;
use crate::eval::tests::eval_out;
use crate::eval::tests::{eval_out, try_eval_out_with_version};
use ergotree_ir::chain::context::Context;
use ergotree_ir::ergo_tree::ErgoTreeVersion;
use ergotree_ir::mir::bin_op::{ArithOp, BinOp, BinOpKind};
use ergotree_ir::mir::constant::Constant;
use ergotree_ir::mir::expr::Expr;
use ergotree_ir::mir::extract_reg_as::ExtractRegisterAs;
use ergotree_ir::mir::get_var::GetVar;
use ergotree_ir::mir::global_vars::GlobalVars;
use ergotree_ir::mir::value::Value;
use ergotree_ir::types::stype::SType;
use sigma_test_util::force_any_val;

Expand Down Expand Up @@ -71,4 +78,39 @@ mod tests {
let v = eval_out::<i64>(&option_get_expr, &ctx);
assert_eq!(v, 1i64);
}
#[test]
fn eval_lazy() {
let get_reg_expr: Expr = ExtractRegisterAs::new(
GlobalVars::SelfBox.into(),
0,
SType::SOption(SType::SLong.into()),
)
.unwrap()
.into();
let divide_by_zero = Expr::BinOp(
BinOp {
kind: BinOpKind::Arith(ArithOp::Divide),
left: Box::new(Constant::from(1i64).into()),
right: Box::new(Constant::from(0i64).into()),
}
.into(),
);
let option_get_expr: Expr = OptionGetOrElse::new(get_reg_expr, divide_by_zero)
.unwrap()
.into();
let ctx = force_any_val::<Context>();
for tree_version in 0..ErgoTreeVersion::V3.into() {
assert!(
try_eval_out_with_version::<Value>(&option_get_expr, &ctx, tree_version, 3)
.is_err()
);
}
for tree_version in ErgoTreeVersion::V3.into()..=ErgoTreeVersion::MAX_SCRIPT_VERSION.into()
{
assert_eq!(
try_eval_out_with_version::<i64>(&option_get_expr, &ctx, tree_version, 3).unwrap(),
ctx.self_box.value.as_i64()
);
}
}
}
34 changes: 18 additions & 16 deletions ergotree-interpreter/src/eval/savltree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,12 @@ pub(crate) static KEY_LENGTH_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {

pub(crate) static VALUE_LENGTH_OPT_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
let avl_tree_data = obj.try_extract_into::<AvlTreeData>()?;
Ok(Value::Opt(Box::new(
Ok(Value::Opt(
avl_tree_data
.value_length_opt
.map(|v| Value::Int(*v as i32)),
)))
.map(|v| Value::Int(*v as i32))
.map(Arc::new),
))
};

pub(crate) static IS_INSERT_ALLOWED_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
Expand Down Expand Up @@ -125,10 +126,10 @@ pub(crate) static GET_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, args| {

match bv.perform_one_operation(&Operation::Lookup(Bytes::from(key))) {
Ok(opt) => match opt {
Some(v) => Ok(Value::Opt(Box::new(Some(Value::Coll(
Some(v) => Ok(Value::Opt(Some(Arc::new(Value::Coll(
CollKind::NativeColl(NativeColl::CollByte(v.iter().map(|&b| b as i8).collect())),
))))),
_ => Ok(Value::Opt(Box::new(None))),
_ => Ok(Value::Opt(None)),
},
Err(_) => Err(EvalError::AvlTree(format!(
"Tree proof is incorrect {:?}",
Expand Down Expand Up @@ -176,13 +177,13 @@ pub(crate) static GET_MANY_EVAL_FN: EvalFn =
.map(|key| {
if let Ok(r) = bv.perform_one_operation(&Operation::Lookup(Bytes::from(key))) {
if let Some(v) = r {
Ok(Value::Opt(Box::new(Some(Value::Coll(
Ok(Value::Opt(Some(Arc::new(Value::Coll(
CollKind::NativeColl(NativeColl::CollByte(
v.iter().map(|&b| b as i8).collect(),
)),
)))))
} else {
Ok(Value::Opt(Box::new(None)))
Ok(Value::Opt(None))
}
} else {
Err(EvalError::AvlTree(format!(
Expand Down Expand Up @@ -254,7 +255,7 @@ pub(crate) static INSERT_EVAL_FN: EvalFn =
if let Some(new_digest) = bv.digest() {
let digest = ADDigest::scorex_parse_bytes(&new_digest)?;
avl_tree_data.digest = digest;
Ok(Value::Opt(Box::new(Some(Value::AvlTree(
Ok(Value::Opt(Some(Arc::new(Value::AvlTree(
avl_tree_data.into(),
)))))
} else {
Expand Down Expand Up @@ -314,7 +315,7 @@ pub(crate) static REMOVE_EVAL_FN: EvalFn =
if let Some(new_digest) = bv.digest() {
let digest = ADDigest::scorex_parse_bytes(&new_digest)?;
avl_tree_data.digest = digest;
Ok(Value::Opt(Box::new(Some(Value::AvlTree(
Ok(Value::Opt(Some(Arc::new(Value::AvlTree(
avl_tree_data.into(),
)))))
} else {
Expand Down Expand Up @@ -424,7 +425,7 @@ pub(crate) static UPDATE_EVAL_FN: EvalFn =
if let Some(new_digest) = bv.digest() {
let digest = ADDigest::scorex_parse_bytes(&new_digest)?;
avl_tree_data.digest = digest;
Ok(Value::Opt(Box::new(Some(Value::AvlTree(
Ok(Value::Opt(Some(Arc::new(Value::AvlTree(
avl_tree_data.into(),
)))))
} else {
Expand Down Expand Up @@ -511,7 +512,8 @@ mod tests {
let res_not_found = eval_out_wo_ctx::<Value>(&expr_not_found);

if let Value::Opt(opt) = res_found {
if let Some(Value::Coll(CollKind::NativeColl(NativeColl::CollByte(b)))) = *opt {
if let Some(Value::Coll(CollKind::NativeColl(NativeColl::CollByte(b)))) = opt.as_deref()
{
assert!(lookup_found.unwrap().eq(&b.as_vec_u8()));
} else {
unreachable!();
Expand Down Expand Up @@ -592,7 +594,7 @@ mod tests {
if let Value::Coll(CollKind::WrappedColl { items, .. }) = res {
for (item, expected) in items.iter().zip(lookups) {
if let Value::Opt(opt) = item.clone() {
match *opt {
match opt.as_deref() {
None => assert!(expected.is_none()),
Some(Value::Coll(CollKind::NativeColl(NativeColl::CollByte(b)))) => {
assert_eq!(&b[..], &expected.unwrap().to_vec().as_vec_i8()[..]);
Expand Down Expand Up @@ -685,7 +687,7 @@ mod tests {

let res = eval_out_wo_ctx::<Value>(&expr);
if let Value::Opt(opt) = res {
if let Some(Value::AvlTree(avl)) = *opt {
if let Some(Value::AvlTree(avl)) = opt.as_deref() {
assert_eq!(avl.digest, final_digest);
} else {
unreachable!();
Expand Down Expand Up @@ -773,7 +775,7 @@ mod tests {

let res = eval_out_wo_ctx::<Value>(&expr);
if let Value::Opt(opt) = res {
assert_eq!(*opt, value_length_opt);
assert_eq!(opt.as_deref().cloned(), value_length_opt);
} else {
unreachable!();
}
Expand Down Expand Up @@ -966,7 +968,7 @@ mod tests {

let res = eval_out_wo_ctx::<Value>(&expr);
if let Value::Opt(opt) = res {
if let Some(Value::AvlTree(avl)) = *opt {
if let Some(Value::AvlTree(avl)) = opt.as_deref() {
assert_eq!(avl.digest, final_digest);
} else {
unreachable!();
Expand Down Expand Up @@ -1043,7 +1045,7 @@ mod tests {

let res = eval_out_wo_ctx::<Value>(&expr);
if let Value::Opt(opt) = res {
if let Some(Value::AvlTree(avl)) = *opt {
if let Some(Value::AvlTree(avl)) = opt.as_deref() {
assert_eq!(avl.digest, final_digest);
} else {
unreachable!();
Expand Down
6 changes: 3 additions & 3 deletions ergotree-interpreter/src/eval/sbox.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::eval::EvalError;

use alloc::boxed::Box;
use alloc::string::ToString;
use alloc::sync::Arc;
use ergotree_ir::chain::ergo_box::ErgoBox;
use ergotree_ir::ergo_tree::ErgoTreeVersion;
use ergotree_ir::mir::constant::TryExtractInto;
Expand Down Expand Up @@ -57,13 +57,13 @@ pub(crate) static GET_REG_EVAL_FN: EvalFn = |mc, _env, ctx, obj, args| {
};
match reg_val_opt {
Some(constant) if constant.tpe == **expected_type => {
Ok(Value::Opt(Box::new(Some(constant.v.into()))))
Ok(Value::Opt(Some(Arc::new(constant.v.into()))))
}
Some(constant) => Err(EvalError::UnexpectedValue(format!(
"Expected register {reg_id} to be of type {}, got {}",
expected_type, constant.tpe
))),
None => Ok(Value::Opt(Box::new(None))),
None => Ok(Value::Opt(None)),
}
};

Expand Down
Loading
Loading