use crate::classic::clvm::__type_compatibility__::{bi_one, bi_zero};
use crate::classic::clvm::sexp::{enlist, proper_list};
use crate::compiler::gensym::gensym;
use crate::util::Number;
use clvm_rs::allocator::{Allocator, NodePtr, SExp};
use clvm_rs::reduction::EvalErr;
use num_bigint::ToBigInt;
use std::collections::HashMap;
pub fn is_at_capture(
allocator: &mut Allocator,
tree_first: NodePtr,
tree_rest: NodePtr,
) -> Option<(NodePtr, NodePtr)> {
if let (SExp::Atom(a), Some(spec)) = (
allocator.sexp(tree_first),
proper_list(allocator, tree_rest, true),
) {
if allocator.buf(&a) == [b'@'] && spec.len() == 2 {
return Some((spec[0], spec[1]));
}
}
None
}
fn wrap_in_unquote(allocator: &mut Allocator, code: NodePtr) -> Result<NodePtr, EvalErr> {
let unquote_atom = allocator.new_atom("unquote".as_bytes())?;
enlist(allocator, &[unquote_atom, code])
}
fn wrap_in_compile_time_list(allocator: &mut Allocator, code: NodePtr) -> Result<NodePtr, EvalErr> {
let chia_enlist_atom = allocator.new_atom("__chia__enlist".as_bytes())?;
enlist(allocator, &[chia_enlist_atom, code])
}
fn create_path_selection_plan(path: Number, operators: &mut Vec<bool>) -> Result<(), EvalErr> {
if path <= bi_one() {
Ok(())
} else {
operators.push(path.clone() % 2_u32.to_bigint().unwrap() == bi_one());
create_path_selection_plan(path / 2_u32.to_bigint().unwrap(), operators)
}
}
fn wrap_path_selection(
allocator: &mut Allocator,
path: Number,
wrapped: NodePtr,
) -> Result<NodePtr, EvalErr> {
let mut operator_stack = Vec::new();
let mut tail = wrapped;
create_path_selection_plan(path, &mut operator_stack)?;
for o in operator_stack.iter() {
let head_op = if *o { vec![6] } else { vec![5] };
let head_atom = allocator.new_atom(&head_op)?;
tail = enlist(allocator, &[head_atom, tail])?;
}
Ok(tail)
}
fn formulate_path_selections_for_destructuring_arg(
allocator: &mut Allocator,
arg_sexp: NodePtr,
arg_path: Number,
arg_depth: Number,
referenced_from: Option<NodePtr>,
selections: &mut HashMap<Vec<u8>, NodePtr>,
) -> Result<NodePtr, EvalErr> {
match allocator.sexp(arg_sexp) {
SExp::Pair(a, b) => {
let next_depth = arg_depth.clone() * 2_u32.to_bigint().unwrap();
if let Some((capture, substructure)) = is_at_capture(allocator, a, b) {
if let SExp::Atom(cbuf) = allocator.sexp(capture) {
let (new_arg_path, new_arg_depth, tail) =
if let Some(prev_ref) = referenced_from {
(arg_path, arg_depth, prev_ref)
} else {
let capture_code = wrap_in_unquote(allocator, capture)?;
let qtail =
wrap_path_selection(allocator, arg_path + arg_depth, capture_code)?;
(bi_zero(), bi_one(), qtail)
};
selections.insert(allocator.buf(&cbuf).to_vec(), tail);
return formulate_path_selections_for_destructuring_arg(
allocator,
substructure,
new_arg_path,
new_arg_depth,
Some(tail),
selections,
)
.map(|_| arg_sexp);
}
}
if referenced_from.is_some() {
let f = formulate_path_selections_for_destructuring_arg(
allocator,
a,
arg_path.clone(),
next_depth.clone(),
referenced_from,
selections,
)?;
let r = formulate_path_selections_for_destructuring_arg(
allocator,
b,
arg_depth + arg_path,
next_depth,
referenced_from,
selections,
)?;
allocator.new_pair(f, r)
} else {
let ref_name = gensym("destructuring_capture".as_bytes().to_vec());
let at_atom = allocator.new_atom("@".as_bytes())?;
let name_atom = allocator.new_atom(&ref_name)?;
let new_arg_list = enlist(allocator, &[at_atom, name_atom, arg_sexp])?;
formulate_path_selections_for_destructuring_arg(
allocator,
new_arg_list,
bi_zero(),
bi_one(),
None,
selections,
)
}
}
SExp::Atom(b) => {
let buf = allocator.buf(&b).to_vec();
if !buf.is_empty() {
if let Some(capture) = referenced_from {
let tail = wrap_path_selection(allocator, arg_path + arg_depth, capture)?;
selections.insert(buf, tail);
return Ok(arg_sexp);
}
}
Ok(arg_sexp)
}
}
}
pub fn formulate_path_selections_for_destructuring(
allocator: &mut Allocator,
args_sexp: NodePtr,
selections: &mut HashMap<Vec<u8>, NodePtr>,
) -> Result<NodePtr, EvalErr> {
if let SExp::Pair(a, b) = allocator.sexp(args_sexp) {
if let Some((capture, substructure)) = is_at_capture(allocator, a, b) {
if let SExp::Atom(cbuf) = allocator.sexp(capture) {
let quoted_arg_list = wrap_in_unquote(allocator, capture)?;
let tail = wrap_in_compile_time_list(allocator, quoted_arg_list)?;
let buf = allocator.buf(&cbuf);
selections.insert(buf.to_vec(), tail);
let newsub = formulate_path_selections_for_destructuring_arg(
allocator,
substructure,
bi_zero(),
bi_one(),
Some(tail),
selections,
)?;
return enlist(allocator, &[a, capture, newsub]);
}
}
let f = formulate_path_selections_for_destructuring_arg(
allocator,
a,
bi_zero(),
bi_one(),
None,
selections,
)?;
let r = formulate_path_selections_for_destructuring(allocator, b, selections)?;
allocator.new_pair(f, r)
} else {
Ok(args_sexp)
}
}
pub fn is_inline_destructure(allocator: &mut Allocator, args_sexp: NodePtr) -> bool {
if let SExp::Pair(a, b) = allocator.sexp(args_sexp) {
if let SExp::Pair(_, _) = allocator.sexp(a) {
return true;
}
return is_inline_destructure(allocator, b);
}
false
}