1838 lines
63 KiB
Rust
1838 lines
63 KiB
Rust
use std::{collections::HashMap, rc::Rc};
|
|
|
|
use indexmap::{IndexMap, IndexSet};
|
|
use itertools::Itertools;
|
|
use uplc::{
|
|
ast::{Constant as UplcConstant, Name, Term, Type as UplcType},
|
|
builder::{CONSTR_FIELDS_EXPOSER, CONSTR_INDEX_EXPOSER},
|
|
builtins::DefaultFunction,
|
|
machine::{
|
|
runtime::{convert_constr_to_tag, ANY_TAG},
|
|
value::to_pallas_bigint,
|
|
},
|
|
Constr, KeyValuePairs, PlutusData,
|
|
};
|
|
|
|
use crate::{
|
|
ast::{
|
|
AssignmentKind, DataType, Pattern, Span, TypedArg, TypedClause, TypedClauseGuard,
|
|
TypedDataType, TypedPattern,
|
|
},
|
|
builtins::{bool, void},
|
|
expr::TypedExpr,
|
|
tipo::{PatternConstructor, TypeVar, ValueConstructor, ValueConstructorVariant},
|
|
};
|
|
|
|
use crate::{
|
|
ast::{BinOp, ClauseGuard, Constant, UnOp},
|
|
tipo::Type,
|
|
};
|
|
|
|
use super::tree::{AirExpression, AirStatement, AirTree, TreePath};
|
|
|
|
pub type Variant = String;
|
|
|
|
pub type Params = Vec<String>;
|
|
|
|
pub type CycleFunctionNames = Vec<String>;
|
|
|
|
pub const TOO_MANY_ITEMS: &str = "TOO_MANY_ITEMS";
|
|
pub const LIST_NOT_EMPTY: &str = "LIST_NOT_EMPTY";
|
|
pub const CONSTR_NOT_EMPTY: &str = "CONSTR_NOT_EMPTY";
|
|
pub const INCORRECT_BOOLEAN: &str = "INCORRECT_BOOLEAN";
|
|
pub const INCORRECT_CONSTR: &str = "INCORRECT_CONSTR";
|
|
pub const CONSTR_INDEX_MISMATCH: &str = "CONSTR_INDEX_MISMATCH";
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum CodeGenFunction {
|
|
Function { body: AirTree, params: Params },
|
|
Link(Variant),
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum HoistableFunction {
|
|
Function {
|
|
body: AirTree,
|
|
deps: Vec<(FunctionAccessKey, Variant)>,
|
|
params: Params,
|
|
},
|
|
CyclicFunction {
|
|
functions: Vec<(Params, AirTree)>,
|
|
deps: Vec<(FunctionAccessKey, Variant)>,
|
|
},
|
|
Link((FunctionAccessKey, Variant)),
|
|
CyclicLink(FunctionAccessKey),
|
|
}
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
|
pub struct DataTypeKey {
|
|
pub module_name: String,
|
|
pub defined_type: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
|
pub struct FunctionAccessKey {
|
|
pub module_name: String,
|
|
pub function_name: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct AssignmentProperties {
|
|
pub value_type: Rc<Type>,
|
|
pub kind: AssignmentKind,
|
|
pub remove_unused: bool,
|
|
pub full_check: bool,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct ClauseProperties {
|
|
pub clause_var_name: String,
|
|
pub complex_clause: bool,
|
|
pub needs_constr_var: bool,
|
|
pub original_subject_name: String,
|
|
pub final_clause: bool,
|
|
pub specific_clause: SpecificClause,
|
|
}
|
|
#[derive(Clone, Debug)]
|
|
pub enum SpecificClause {
|
|
ConstrClause,
|
|
ListClause {
|
|
defined_tails_index: i64,
|
|
defined_tails: Vec<String>,
|
|
checked_index: i64,
|
|
},
|
|
TupleClause {
|
|
defined_tuple_indices: IndexSet<(usize, String)>,
|
|
},
|
|
}
|
|
|
|
impl ClauseProperties {
|
|
pub fn init(t: &Rc<Type>, constr_var: String, subject_name: String) -> Self {
|
|
if t.is_list() {
|
|
ClauseProperties {
|
|
clause_var_name: constr_var,
|
|
complex_clause: false,
|
|
original_subject_name: subject_name.clone(),
|
|
final_clause: false,
|
|
needs_constr_var: false,
|
|
specific_clause: SpecificClause::ListClause {
|
|
defined_tails_index: 0,
|
|
defined_tails: vec![subject_name],
|
|
checked_index: -1,
|
|
},
|
|
}
|
|
} else if t.is_tuple() {
|
|
ClauseProperties {
|
|
clause_var_name: constr_var,
|
|
complex_clause: false,
|
|
original_subject_name: subject_name,
|
|
needs_constr_var: false,
|
|
final_clause: false,
|
|
specific_clause: SpecificClause::TupleClause {
|
|
defined_tuple_indices: IndexSet::new(),
|
|
},
|
|
}
|
|
} else {
|
|
ClauseProperties {
|
|
clause_var_name: constr_var,
|
|
complex_clause: false,
|
|
original_subject_name: subject_name,
|
|
needs_constr_var: false,
|
|
final_clause: false,
|
|
specific_clause: SpecificClause::ConstrClause,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn init_inner(
|
|
t: &Rc<Type>,
|
|
constr_var: String,
|
|
subject_name: String,
|
|
final_clause: bool,
|
|
) -> Self {
|
|
if t.is_list() {
|
|
ClauseProperties {
|
|
clause_var_name: constr_var,
|
|
complex_clause: false,
|
|
original_subject_name: subject_name,
|
|
final_clause,
|
|
needs_constr_var: false,
|
|
specific_clause: SpecificClause::ListClause {
|
|
defined_tails_index: 0,
|
|
defined_tails: vec![],
|
|
checked_index: -1,
|
|
},
|
|
}
|
|
} else if t.is_tuple() {
|
|
ClauseProperties {
|
|
clause_var_name: constr_var,
|
|
complex_clause: false,
|
|
original_subject_name: subject_name,
|
|
needs_constr_var: false,
|
|
final_clause,
|
|
specific_clause: SpecificClause::TupleClause {
|
|
defined_tuple_indices: IndexSet::new(),
|
|
},
|
|
}
|
|
} else {
|
|
ClauseProperties {
|
|
clause_var_name: constr_var,
|
|
complex_clause: false,
|
|
original_subject_name: subject_name,
|
|
needs_constr_var: false,
|
|
final_clause,
|
|
specific_clause: SpecificClause::ConstrClause,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct CodeGenSpecialFuncs {
|
|
pub used_funcs: Vec<String>,
|
|
pub key_to_func: IndexMap<String, Term<Name>>,
|
|
}
|
|
|
|
impl CodeGenSpecialFuncs {
|
|
pub fn new() -> Self {
|
|
let mut key_to_func = IndexMap::new();
|
|
|
|
key_to_func.insert(
|
|
CONSTR_FIELDS_EXPOSER.to_string(),
|
|
Term::snd_pair()
|
|
.apply(Term::unconstr_data().apply(Term::var("__constr_var")))
|
|
.lambda("__constr_var"),
|
|
);
|
|
|
|
key_to_func.insert(
|
|
CONSTR_INDEX_EXPOSER.to_string(),
|
|
Term::fst_pair()
|
|
.apply(Term::unconstr_data().apply(Term::var("__constr_var")))
|
|
.lambda("__constr_var"),
|
|
);
|
|
|
|
key_to_func.insert(
|
|
TOO_MANY_ITEMS.to_string(),
|
|
Term::string("List/Tuple/Constr contains more items than expected"),
|
|
);
|
|
|
|
key_to_func.insert(
|
|
LIST_NOT_EMPTY.to_string(),
|
|
Term::string("Expected no items for List"),
|
|
);
|
|
|
|
key_to_func.insert(
|
|
CONSTR_NOT_EMPTY.to_string(),
|
|
Term::string("Expected no fields for Constr"),
|
|
);
|
|
|
|
key_to_func.insert(
|
|
INCORRECT_BOOLEAN.to_string(),
|
|
Term::string("Expected on incorrect Boolean variant"),
|
|
);
|
|
|
|
key_to_func.insert(
|
|
INCORRECT_CONSTR.to_string(),
|
|
Term::string("Expected on incorrect Constr variant"),
|
|
);
|
|
|
|
key_to_func.insert(
|
|
CONSTR_INDEX_MISMATCH.to_string(),
|
|
Term::string("Constr index didn't match a type variant"),
|
|
);
|
|
|
|
CodeGenSpecialFuncs {
|
|
used_funcs: vec![],
|
|
key_to_func,
|
|
}
|
|
}
|
|
|
|
pub fn use_function(&mut self, func_name: &'static str) -> &'static str {
|
|
if !self.used_funcs.contains(&func_name.to_string()) {
|
|
self.used_funcs.push(func_name.to_string());
|
|
}
|
|
func_name
|
|
}
|
|
|
|
pub fn get_function(&self, func_name: &String) -> Term<Name> {
|
|
self.key_to_func[func_name].clone()
|
|
}
|
|
|
|
pub fn apply_used_functions(&self, mut term: Term<Name>) -> Term<Name> {
|
|
for func_name in self.used_funcs.iter() {
|
|
term = term.lambda(func_name).apply(self.get_function(func_name));
|
|
}
|
|
term
|
|
}
|
|
}
|
|
|
|
impl Default for CodeGenSpecialFuncs {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
pub fn get_generic_id_and_type(tipo: &Type, param: &Type) -> Vec<(u64, Rc<Type>)> {
|
|
let mut generics_ids = vec![];
|
|
|
|
if let Some(id) = tipo.get_generic() {
|
|
generics_ids.push((id, param.clone().into()));
|
|
return generics_ids;
|
|
}
|
|
|
|
for (tipo, param_type) in tipo
|
|
.get_inner_types()
|
|
.iter()
|
|
.zip(param.get_inner_types().iter())
|
|
{
|
|
generics_ids.append(&mut get_generic_id_and_type(tipo, param_type));
|
|
}
|
|
generics_ids
|
|
}
|
|
|
|
pub fn lookup_data_type_by_tipo(
|
|
data_types: &IndexMap<DataTypeKey, &TypedDataType>,
|
|
tipo: &Type,
|
|
) -> Option<DataType<Rc<Type>>> {
|
|
match tipo {
|
|
Type::Fn { ret, .. } => match ret.as_ref() {
|
|
Type::App { module, name, .. } => {
|
|
let data_type_key = DataTypeKey {
|
|
module_name: module.clone(),
|
|
defined_type: name.clone(),
|
|
};
|
|
data_types.get(&data_type_key).map(|item| (*item).clone())
|
|
}
|
|
_ => None,
|
|
},
|
|
Type::App { module, name, .. } => {
|
|
let data_type_key = DataTypeKey {
|
|
module_name: module.clone(),
|
|
defined_type: name.clone(),
|
|
};
|
|
|
|
data_types.get(&data_type_key).map(|item| (*item).clone())
|
|
}
|
|
Type::Var { tipo } => {
|
|
if let TypeVar::Link { tipo } = &*tipo.borrow() {
|
|
lookup_data_type_by_tipo(data_types, tipo)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn get_arg_type_name(tipo: &Type) -> String {
|
|
match tipo {
|
|
Type::App { name, args, .. } => {
|
|
let inner_args = args.iter().map(|arg| get_arg_type_name(arg)).collect_vec();
|
|
format!("{}_{}", name, inner_args.join("_"))
|
|
}
|
|
Type::Var { tipo } => match tipo.borrow().clone() {
|
|
TypeVar::Link { tipo } => get_arg_type_name(tipo.as_ref()),
|
|
_ => unreachable!(),
|
|
},
|
|
Type::Tuple { elems } => {
|
|
let inner_args = elems.iter().map(|arg| get_arg_type_name(arg)).collect_vec();
|
|
inner_args.join("_")
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
pub fn convert_opaque_type(
|
|
t: &Rc<Type>,
|
|
data_types: &IndexMap<DataTypeKey, &TypedDataType>,
|
|
) -> Rc<Type> {
|
|
if check_replaceable_opaque_type(t, data_types) && matches!(t.as_ref(), Type::App { .. }) {
|
|
let data_type = lookup_data_type_by_tipo(data_types, t).unwrap();
|
|
let new_type_fields = data_type.typed_parameters;
|
|
|
|
let mut mono_type_vec = vec![];
|
|
|
|
for (tipo, param) in new_type_fields.iter().zip(t.arg_types().unwrap()) {
|
|
mono_type_vec.append(&mut get_generic_id_and_type(tipo, ¶m));
|
|
}
|
|
let mono_types = mono_type_vec.into_iter().collect();
|
|
|
|
let generic_type = &data_type.constructors[0].arguments[0].tipo;
|
|
|
|
let mono_type = find_and_replace_generics(generic_type, &mono_types);
|
|
|
|
convert_opaque_type(&mono_type, data_types)
|
|
} else {
|
|
match t.as_ref() {
|
|
Type::App {
|
|
public,
|
|
module,
|
|
name,
|
|
args,
|
|
} => {
|
|
let mut new_args = vec![];
|
|
for arg in args {
|
|
let arg = convert_opaque_type(arg, data_types);
|
|
new_args.push(arg);
|
|
}
|
|
Type::App {
|
|
public: *public,
|
|
module: module.clone(),
|
|
name: name.clone(),
|
|
args: new_args,
|
|
}
|
|
.into()
|
|
}
|
|
Type::Fn { args, ret } => {
|
|
let mut new_args = vec![];
|
|
for arg in args {
|
|
let arg = convert_opaque_type(arg, data_types);
|
|
new_args.push(arg);
|
|
}
|
|
|
|
let ret = convert_opaque_type(ret, data_types);
|
|
|
|
Type::Fn {
|
|
args: new_args,
|
|
ret,
|
|
}
|
|
.into()
|
|
}
|
|
Type::Var { tipo: var_tipo } => {
|
|
if let TypeVar::Link { tipo } = &var_tipo.borrow().clone() {
|
|
convert_opaque_type(tipo, data_types)
|
|
} else {
|
|
t.clone()
|
|
}
|
|
}
|
|
Type::Tuple { elems } => {
|
|
let mut new_elems = vec![];
|
|
for arg in elems {
|
|
let arg = convert_opaque_type(arg, data_types);
|
|
new_elems.push(arg);
|
|
}
|
|
Type::Tuple { elems: new_elems }.into()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn check_replaceable_opaque_type(
|
|
t: &Rc<Type>,
|
|
data_types: &IndexMap<DataTypeKey, &TypedDataType>,
|
|
) -> bool {
|
|
let data_type = lookup_data_type_by_tipo(data_types, t);
|
|
|
|
if let Some(data_type) = data_type {
|
|
assert!(!data_type.constructors.is_empty());
|
|
let data_type_args = &data_type.constructors[0].arguments;
|
|
data_type_args.len() == 1 && data_type.opaque && data_type.constructors.len() == 1
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
pub fn find_and_replace_generics(
|
|
tipo: &Rc<Type>,
|
|
mono_types: &IndexMap<u64, Rc<Type>>,
|
|
) -> Rc<Type> {
|
|
if let Some(id) = tipo.get_generic() {
|
|
// If a generic does not have a type we know of
|
|
// like a None in option then just use same type
|
|
mono_types.get(&id).unwrap_or(tipo).clone()
|
|
} else if tipo.is_generic() {
|
|
match &**tipo {
|
|
Type::App {
|
|
args,
|
|
public,
|
|
module,
|
|
name,
|
|
} => {
|
|
let mut new_args = vec![];
|
|
for arg in args {
|
|
let arg = find_and_replace_generics(arg, mono_types);
|
|
new_args.push(arg);
|
|
}
|
|
let t = Type::App {
|
|
args: new_args,
|
|
public: *public,
|
|
module: module.clone(),
|
|
name: name.clone(),
|
|
};
|
|
t.into()
|
|
}
|
|
Type::Fn { args, ret } => {
|
|
let mut new_args = vec![];
|
|
for arg in args {
|
|
let arg = find_and_replace_generics(arg, mono_types);
|
|
new_args.push(arg);
|
|
}
|
|
|
|
let ret = find_and_replace_generics(ret, mono_types);
|
|
|
|
let t = Type::Fn {
|
|
args: new_args,
|
|
ret,
|
|
};
|
|
|
|
t.into()
|
|
}
|
|
Type::Tuple { elems } => {
|
|
let mut new_elems = vec![];
|
|
for elem in elems {
|
|
let elem = find_and_replace_generics(elem, mono_types);
|
|
new_elems.push(elem);
|
|
}
|
|
let t = Type::Tuple { elems: new_elems };
|
|
t.into()
|
|
}
|
|
Type::Var { tipo: var_tipo } => {
|
|
let var_type = var_tipo.as_ref().borrow().clone();
|
|
|
|
match var_type {
|
|
TypeVar::Link { tipo } => find_and_replace_generics(&tipo, mono_types),
|
|
TypeVar::Generic { .. } | TypeVar::Unbound { .. } => unreachable!(),
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
tipo.clone()
|
|
}
|
|
}
|
|
|
|
pub fn constants_ir(literal: &Constant) -> AirTree {
|
|
match literal {
|
|
Constant::Int { value, .. } => AirTree::int(value),
|
|
Constant::String { value, .. } => AirTree::string(value),
|
|
Constant::ByteArray { bytes, .. } => AirTree::byte_array(bytes.clone()),
|
|
}
|
|
}
|
|
|
|
pub fn handle_clause_guard(clause_guard: &TypedClauseGuard) -> AirTree {
|
|
match clause_guard {
|
|
ClauseGuard::Not { value, .. } => {
|
|
let val = handle_clause_guard(value);
|
|
|
|
AirTree::unop(UnOp::Not, val)
|
|
}
|
|
ClauseGuard::Equals { left, right, .. } => {
|
|
let left_child = handle_clause_guard(left);
|
|
let right_child = handle_clause_guard(right);
|
|
|
|
AirTree::binop(BinOp::Eq, bool(), left_child, right_child, left.tipo())
|
|
}
|
|
ClauseGuard::NotEquals { left, right, .. } => {
|
|
let left_child = handle_clause_guard(left);
|
|
let right_child = handle_clause_guard(right);
|
|
|
|
AirTree::binop(BinOp::NotEq, bool(), left_child, right_child, left.tipo())
|
|
}
|
|
ClauseGuard::GtInt { left, right, .. } => {
|
|
let left_child = handle_clause_guard(left);
|
|
let right_child = handle_clause_guard(right);
|
|
|
|
AirTree::binop(BinOp::GtInt, bool(), left_child, right_child, left.tipo())
|
|
}
|
|
ClauseGuard::GtEqInt { left, right, .. } => {
|
|
let left_child = handle_clause_guard(left);
|
|
let right_child = handle_clause_guard(right);
|
|
|
|
AirTree::binop(BinOp::GtEqInt, bool(), left_child, right_child, left.tipo())
|
|
}
|
|
ClauseGuard::LtInt { left, right, .. } => {
|
|
let left_child = handle_clause_guard(left);
|
|
let right_child = handle_clause_guard(right);
|
|
|
|
AirTree::binop(BinOp::LtInt, bool(), left_child, right_child, left.tipo())
|
|
}
|
|
ClauseGuard::LtEqInt { left, right, .. } => {
|
|
let left_child = handle_clause_guard(left);
|
|
let right_child = handle_clause_guard(right);
|
|
|
|
AirTree::binop(BinOp::LtEqInt, bool(), left_child, right_child, left.tipo())
|
|
}
|
|
ClauseGuard::Or { left, right, .. } => {
|
|
let left_child = handle_clause_guard(left);
|
|
let right_child = handle_clause_guard(right);
|
|
|
|
AirTree::binop(BinOp::Or, bool(), left_child, right_child, left.tipo())
|
|
}
|
|
ClauseGuard::And { left, right, .. } => {
|
|
let left_child = handle_clause_guard(left);
|
|
let right_child = handle_clause_guard(right);
|
|
|
|
AirTree::binop(BinOp::And, bool(), left_child, right_child, left.tipo())
|
|
}
|
|
ClauseGuard::Var { tipo, name, .. } => AirTree::local_var(name, tipo.clone()),
|
|
ClauseGuard::Constant(constant) => constants_ir(constant),
|
|
}
|
|
}
|
|
|
|
pub fn get_variant_name(t: &Rc<Type>) -> String {
|
|
if t.is_string() {
|
|
"_string".to_string()
|
|
} else if t.is_int() {
|
|
"_int".to_string()
|
|
} else if t.is_bool() {
|
|
"_bool".to_string()
|
|
} else if t.is_bytearray() {
|
|
"_bytearray".to_string()
|
|
} else if t.is_map() {
|
|
let mut full_type = vec!["_map".to_string()];
|
|
let pair_type = &t.get_inner_types()[0];
|
|
let fst_type = &pair_type.get_inner_types()[0];
|
|
let snd_type = &pair_type.get_inner_types()[1];
|
|
full_type.push(get_variant_name(fst_type));
|
|
full_type.push(get_variant_name(snd_type));
|
|
full_type.join("")
|
|
} else if t.is_list() {
|
|
let full_type = "_list".to_string();
|
|
let list_type = &t.get_inner_types()[0];
|
|
|
|
format!("{}{}", full_type, get_variant_name(list_type))
|
|
} else if t.is_tuple() {
|
|
let mut full_type = vec!["_tuple".to_string()];
|
|
|
|
let inner_types = t.get_inner_types();
|
|
|
|
for arg_type in inner_types {
|
|
full_type.push(get_variant_name(&arg_type));
|
|
}
|
|
full_type.join("")
|
|
} else if t.is_unbound() {
|
|
"_unbound".to_string()
|
|
} else {
|
|
let full_type = "_data".to_string();
|
|
|
|
if t.is_generic() {
|
|
panic!("FOUND A POLYMORPHIC TYPE. EXPECTED MONOMORPHIC TYPE");
|
|
}
|
|
|
|
full_type
|
|
}
|
|
}
|
|
|
|
pub fn monomorphize(air_tree: &mut AirTree, mono_types: &IndexMap<u64, Rc<Type>>) {
|
|
let mut held_types = air_tree.mut_held_types();
|
|
|
|
while let Some(tipo) = held_types.pop() {
|
|
*tipo = find_and_replace_generics(tipo, mono_types);
|
|
}
|
|
}
|
|
|
|
pub fn erase_opaque_type_operations(
|
|
air_tree: &mut AirTree,
|
|
data_types: &IndexMap<DataTypeKey, &TypedDataType>,
|
|
) {
|
|
if let AirTree::Expression(AirExpression::Constr { tipo, args, .. }) = air_tree {
|
|
if check_replaceable_opaque_type(tipo, data_types) {
|
|
let arg = args.pop().unwrap();
|
|
if let AirTree::Expression(AirExpression::CastToData { value, .. }) = arg {
|
|
*air_tree = *value;
|
|
} else {
|
|
*air_tree = arg;
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut held_types = air_tree.mut_held_types();
|
|
|
|
while let Some(tipo) = held_types.pop() {
|
|
*tipo = convert_opaque_type(tipo, data_types);
|
|
}
|
|
}
|
|
|
|
/// Determine whether this air_tree node introduces any shadowing over `potential_matches`
|
|
pub fn find_introduced_variables(air_tree: &AirTree) -> Vec<String> {
|
|
match air_tree {
|
|
AirTree::Statement {
|
|
statement: AirStatement::Let { name, .. },
|
|
..
|
|
} => vec![name.clone()],
|
|
AirTree::Statement {
|
|
statement: AirStatement::TupleGuard { indices, .. },
|
|
..
|
|
}
|
|
| AirTree::Expression(AirExpression::TupleClause { indices, .. }) => {
|
|
indices.iter().map(|(_, name)| name).cloned().collect()
|
|
}
|
|
AirTree::Expression(AirExpression::Fn { params, .. }) => params.to_vec(),
|
|
AirTree::Statement {
|
|
statement: AirStatement::ListAccessor { names, .. },
|
|
..
|
|
} => names.clone(),
|
|
AirTree::Statement {
|
|
statement:
|
|
AirStatement::ListExpose {
|
|
tail,
|
|
tail_head_names,
|
|
..
|
|
},
|
|
..
|
|
} => {
|
|
let mut ret = vec![];
|
|
if let Some((_, head)) = tail {
|
|
ret.push(head.clone())
|
|
}
|
|
|
|
for name in tail_head_names.iter().map(|(_, head)| head) {
|
|
ret.push(name.clone());
|
|
}
|
|
ret
|
|
}
|
|
AirTree::Statement {
|
|
statement: AirStatement::TupleAccessor { names, .. },
|
|
..
|
|
} => names.clone(),
|
|
AirTree::Statement {
|
|
statement: AirStatement::FieldsExpose { indices, .. },
|
|
..
|
|
} => indices.iter().map(|(_, name, _)| name).cloned().collect(),
|
|
_ => vec![],
|
|
}
|
|
}
|
|
|
|
/// Determine whether a function is recursive, and if so, get the arguments
|
|
pub fn is_recursive_function_call<'a>(
|
|
air_tree: &'a AirTree,
|
|
func_key: &FunctionAccessKey,
|
|
variant: &String,
|
|
) -> (bool, Option<&'a Vec<AirTree>>) {
|
|
if let AirTree::Expression(AirExpression::Call { func, args, .. }) = air_tree {
|
|
if let AirTree::Expression(AirExpression::Var {
|
|
constructor:
|
|
ValueConstructor {
|
|
variant: ValueConstructorVariant::ModuleFn { name, module, .. },
|
|
..
|
|
},
|
|
variant_name,
|
|
..
|
|
}) = func.as_ref()
|
|
{
|
|
if name == &func_key.function_name
|
|
&& module == &func_key.module_name
|
|
&& variant == variant_name
|
|
{
|
|
return (true, Some(args));
|
|
}
|
|
}
|
|
}
|
|
(false, None)
|
|
}
|
|
|
|
pub fn identify_recursive_static_params(
|
|
air_tree: &mut AirTree,
|
|
tree_path: &TreePath,
|
|
func_params: &[String],
|
|
func_key: &FunctionAccessKey,
|
|
variant: &String,
|
|
shadowed_parameters: &mut HashMap<String, TreePath>,
|
|
potential_recursive_statics: &mut Vec<String>,
|
|
) {
|
|
// Find whether any of the potential recursive statics get shadowed (because even if we pass in the same referenced name, it might not be static)
|
|
for introduced_variable in find_introduced_variables(air_tree) {
|
|
if potential_recursive_statics.contains(&introduced_variable) {
|
|
shadowed_parameters.insert(introduced_variable, tree_path.clone());
|
|
}
|
|
}
|
|
// Otherwise, if this is a recursive call site, disqualify anything that is different (or the same, but shadowed)
|
|
if let (true, Some(args)) = is_recursive_function_call(air_tree, func_key, variant) {
|
|
for (param, arg) in func_params.iter().zip(args) {
|
|
if let Some((idx, _)) = potential_recursive_statics
|
|
.iter()
|
|
.find_position(|&p| p == param)
|
|
{
|
|
// Check if we pass something different in this recursive call site
|
|
// by different, we mean
|
|
// - a variable that is bound to a different name
|
|
// - a variable with the same name, but that was shadowed in an ancestor scope
|
|
// - any other type of expression
|
|
let param_is_different = match arg {
|
|
AirTree::Expression(AirExpression::Var { name, .. }) => {
|
|
// "shadowed in an ancestor scope" means "the definition scope is a prefix of our scope"
|
|
name != param
|
|
|| if let Some(p) = shadowed_parameters.get(param) {
|
|
p.common_ancestor(tree_path) == *p
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
_ => true,
|
|
};
|
|
// If so, then we disqualify this parameter from being a recursive static parameter
|
|
if param_is_different {
|
|
potential_recursive_statics.remove(idx);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn modify_self_calls(
|
|
body: &mut AirTree,
|
|
func_key: &FunctionAccessKey,
|
|
variant: &String,
|
|
func_params: &[String],
|
|
) -> Vec<String> {
|
|
let mut potential_recursive_statics = func_params.to_vec();
|
|
// identify which parameters are recursively nonstatic (i.e. get modified before the self-call)
|
|
// TODO: this would be a lot simpler if each `Var`, `Let`, function argument, etc. had a unique identifier
|
|
// rather than just a name; this would let us track if the Var passed to itself was the same value as the method argument
|
|
let mut shadowed_parameters: HashMap<String, TreePath> = HashMap::new();
|
|
body.traverse_tree_with(
|
|
&mut |air_tree: &mut AirTree, tree_path| {
|
|
identify_recursive_static_params(
|
|
air_tree,
|
|
tree_path,
|
|
func_params,
|
|
func_key,
|
|
variant,
|
|
&mut shadowed_parameters,
|
|
&mut potential_recursive_statics,
|
|
);
|
|
},
|
|
false,
|
|
);
|
|
|
|
// Find the index of any recursively static parameters,
|
|
// so we can remove them from the call-site of each recursive call
|
|
let recursive_static_indexes: Vec<_> = func_params
|
|
.iter()
|
|
.enumerate()
|
|
.filter(|&(_, p)| potential_recursive_statics.contains(p))
|
|
.map(|(idx, _)| idx)
|
|
.collect();
|
|
|
|
// Modify any self calls to remove recursive static parameters and append `self` as a parameter for the recursion
|
|
body.traverse_tree_with(
|
|
&mut |air_tree: &mut AirTree, _| {
|
|
if let AirTree::Expression(AirExpression::Call { func, args, .. }) = air_tree {
|
|
if let AirTree::Expression(AirExpression::Var {
|
|
constructor:
|
|
ValueConstructor {
|
|
variant: ValueConstructorVariant::ModuleFn { name, module, .. },
|
|
..
|
|
},
|
|
variant_name,
|
|
..
|
|
}) = func.as_ref()
|
|
{
|
|
if name == &func_key.function_name
|
|
&& module == &func_key.module_name
|
|
&& variant == variant_name
|
|
{
|
|
// Remove any static-recursive-parameters, because they'll be bound statically
|
|
// above the recursive part of the function
|
|
// note: assumes that static_recursive_params is sorted
|
|
for arg in recursive_static_indexes.iter().rev() {
|
|
args.remove(*arg);
|
|
}
|
|
let mut new_args = vec![func.as_ref().clone()];
|
|
new_args.append(args);
|
|
*args = new_args;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
true,
|
|
);
|
|
let recursive_nonstatics = func_params
|
|
.iter()
|
|
.filter(|p| !potential_recursive_statics.contains(p))
|
|
.cloned()
|
|
.collect();
|
|
recursive_nonstatics
|
|
}
|
|
|
|
pub fn modify_cyclic_calls(
|
|
body: &mut AirTree,
|
|
func_key: &FunctionAccessKey,
|
|
cyclic_links: &IndexMap<
|
|
(FunctionAccessKey, Variant),
|
|
(CycleFunctionNames, usize, FunctionAccessKey),
|
|
>,
|
|
) {
|
|
body.traverse_tree_with(
|
|
&mut |air_tree: &mut AirTree, _| {
|
|
if let AirTree::Expression(AirExpression::Var {
|
|
constructor:
|
|
ValueConstructor {
|
|
variant: ValueConstructorVariant::ModuleFn { name, module, .. },
|
|
tipo,
|
|
..
|
|
},
|
|
variant_name,
|
|
..
|
|
}) = air_tree
|
|
{
|
|
let tipo = tipo.clone();
|
|
let var_key = FunctionAccessKey {
|
|
module_name: module.clone(),
|
|
function_name: name.clone(),
|
|
};
|
|
|
|
if let Some((names, index, cyclic_name)) =
|
|
cyclic_links.get(&(var_key.clone(), variant_name.to_string()))
|
|
{
|
|
if *cyclic_name == *func_key {
|
|
let cyclic_var_name = if cyclic_name.module_name.is_empty() {
|
|
cyclic_name.function_name.to_string()
|
|
} else {
|
|
format!("{}_{}", cyclic_name.module_name, cyclic_name.function_name)
|
|
};
|
|
|
|
let index_name = names[*index].clone();
|
|
|
|
let var = AirTree::var(
|
|
ValueConstructor::public(
|
|
tipo.clone(),
|
|
ValueConstructorVariant::ModuleFn {
|
|
name: cyclic_var_name.clone(),
|
|
field_map: None,
|
|
module: "".to_string(),
|
|
arity: 2,
|
|
location: Span::empty(),
|
|
builtin: None,
|
|
},
|
|
),
|
|
cyclic_var_name,
|
|
"".to_string(),
|
|
);
|
|
|
|
*air_tree = AirTree::call(
|
|
var.clone(),
|
|
tipo.clone(),
|
|
vec![
|
|
var,
|
|
AirTree::anon_func(
|
|
names.clone(),
|
|
AirTree::local_var(index_name, tipo),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
true,
|
|
);
|
|
}
|
|
|
|
pub fn pattern_has_conditions(
|
|
pattern: &TypedPattern,
|
|
data_types: &IndexMap<DataTypeKey, &TypedDataType>,
|
|
) -> bool {
|
|
match pattern {
|
|
Pattern::List { .. } | Pattern::Int { .. } => true,
|
|
Pattern::Tuple { elems, .. } => elems
|
|
.iter()
|
|
.any(|elem| pattern_has_conditions(elem, data_types)),
|
|
Pattern::Constructor {
|
|
arguments, tipo, ..
|
|
} => {
|
|
let data_type =
|
|
lookup_data_type_by_tipo(data_types, tipo).expect("Data type not found");
|
|
|
|
data_type.constructors.len() > 1
|
|
|| arguments
|
|
.iter()
|
|
.any(|arg| pattern_has_conditions(&arg.value, data_types))
|
|
}
|
|
Pattern::Assign { pattern, .. } => pattern_has_conditions(pattern, data_types),
|
|
Pattern::Var { .. } | Pattern::Discard { .. } => false,
|
|
}
|
|
}
|
|
|
|
// TODO: write some tests
|
|
pub fn rearrange_list_clauses(
|
|
clauses: Vec<TypedClause>,
|
|
data_types: &IndexMap<DataTypeKey, &TypedDataType>,
|
|
) -> Vec<TypedClause> {
|
|
let mut sorted_clauses = clauses;
|
|
|
|
// if we have a list sort clauses so we can plug holes for cases not covered by clauses
|
|
// Now we sort by elements + tail if possible and otherwise leave an index in place if var or discard
|
|
// This is a stable sort. i.e. matching elements amounts will remain in user given order.
|
|
sorted_clauses = sorted_clauses
|
|
.into_iter()
|
|
.enumerate()
|
|
.sorted_by(|(index1, clause1), (index2, clause2)| {
|
|
let mut clause_pattern1 = &clause1.pattern;
|
|
let mut clause_pattern2 = &clause2.pattern;
|
|
|
|
if let Pattern::Assign { pattern, .. } = clause_pattern1 {
|
|
clause_pattern1 = pattern;
|
|
}
|
|
|
|
if let Pattern::Assign { pattern, .. } = clause_pattern2 {
|
|
clause_pattern2 = pattern;
|
|
}
|
|
|
|
let clause1_len = match clause_pattern1 {
|
|
Pattern::List { elements, tail, .. } => {
|
|
Some(elements.len() + usize::from(tail.is_some() && clause1.guard.is_none()))
|
|
}
|
|
_ if clause1.guard.is_none() => Some(100000),
|
|
_ => None,
|
|
};
|
|
|
|
let clause2_len = match clause_pattern2 {
|
|
Pattern::List { elements, tail, .. } => {
|
|
Some(elements.len() + usize::from(tail.is_some() && clause2.guard.is_none()))
|
|
}
|
|
_ if clause2.guard.is_none() => Some(100001),
|
|
_ => None,
|
|
};
|
|
|
|
if let Some(clause1_len) = clause1_len {
|
|
if let Some(clause2_len) = clause2_len {
|
|
return clause1_len.cmp(&clause2_len);
|
|
}
|
|
}
|
|
|
|
index1.cmp(index2)
|
|
})
|
|
.map(|(_, item)| item)
|
|
.collect_vec();
|
|
|
|
let mut final_clauses = sorted_clauses.clone();
|
|
let mut holes_to_fill = vec![];
|
|
let mut last_clause_index = 0;
|
|
let mut last_clause_set = false;
|
|
let mut wild_card_clause_elems = 0;
|
|
|
|
// If we have a catch all, use that. Otherwise use todo which will result in error
|
|
// TODO: fill in todo label with description
|
|
let plug_in_then = &|index: usize, last_clause: &TypedClause| {
|
|
if last_clause.guard.is_none() {
|
|
match &last_clause.pattern {
|
|
Pattern::Var { .. } | Pattern::Discard { .. } => last_clause.clone().then,
|
|
_ => {
|
|
let tipo = last_clause.then.tipo();
|
|
|
|
TypedExpr::Trace {
|
|
location: Span::empty(),
|
|
tipo: tipo.clone(),
|
|
text: Box::new(TypedExpr::String {
|
|
location: Span::empty(),
|
|
tipo: crate::builtins::string(),
|
|
value: format!("Clause hole found for {index} elements."),
|
|
}),
|
|
then: Box::new(TypedExpr::ErrorTerm {
|
|
location: Span::empty(),
|
|
tipo,
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
let tipo = last_clause.then.tipo();
|
|
|
|
TypedExpr::Trace {
|
|
location: Span::empty(),
|
|
tipo: tipo.clone(),
|
|
text: Box::new(TypedExpr::String {
|
|
location: Span::empty(),
|
|
tipo: crate::builtins::string(),
|
|
value: format!("Clause hole found for {index} elements."),
|
|
}),
|
|
then: Box::new(TypedExpr::ErrorTerm {
|
|
location: Span::empty(),
|
|
tipo,
|
|
}),
|
|
}
|
|
}
|
|
};
|
|
|
|
let last_clause = &sorted_clauses[sorted_clauses.len() - 1];
|
|
let assign_plug_in_name = if let Pattern::Var { name, .. } = &last_clause.pattern {
|
|
Some(name)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
for (index, clause) in sorted_clauses.iter().enumerate() {
|
|
if last_clause_set {
|
|
continue;
|
|
}
|
|
|
|
let mut clause_pattern = &clause.pattern;
|
|
|
|
if let Pattern::Assign { pattern, .. } = clause_pattern {
|
|
clause_pattern = pattern;
|
|
}
|
|
|
|
assert!(matches!(
|
|
clause_pattern,
|
|
Pattern::List { .. } | Pattern::Var { .. } | Pattern::Discard { .. }
|
|
));
|
|
|
|
if let Pattern::List { elements, tail, .. } = clause_pattern {
|
|
// found a hole and now we plug it
|
|
while wild_card_clause_elems < elements.len() {
|
|
let mut discard_elems = vec![];
|
|
|
|
for _ in 0..wild_card_clause_elems {
|
|
discard_elems.push(Pattern::Discard {
|
|
name: "__fill".to_string(),
|
|
location: Span::empty(),
|
|
});
|
|
}
|
|
|
|
// If we have a named catch all then in scope the name and create list of discards, otherwise list of discards
|
|
let clause_to_fill = if let Some(name) = assign_plug_in_name {
|
|
TypedClause {
|
|
location: Span::empty(),
|
|
pattern: Pattern::Assign {
|
|
name: name.clone(),
|
|
location: Span::empty(),
|
|
pattern: Pattern::List {
|
|
location: Span::empty(),
|
|
elements: discard_elems,
|
|
tail: None,
|
|
}
|
|
.into(),
|
|
},
|
|
guard: None,
|
|
then: plug_in_then(wild_card_clause_elems, last_clause),
|
|
}
|
|
} else {
|
|
TypedClause {
|
|
location: Span::empty(),
|
|
pattern: Pattern::List {
|
|
location: Span::empty(),
|
|
elements: discard_elems,
|
|
tail: None,
|
|
},
|
|
guard: None,
|
|
then: plug_in_then(wild_card_clause_elems, last_clause),
|
|
}
|
|
};
|
|
|
|
holes_to_fill.push((index, clause_to_fill));
|
|
wild_card_clause_elems += 1;
|
|
}
|
|
|
|
let mut is_wild_card_elems_clause = clause.guard.is_none();
|
|
|
|
for element in elements.iter() {
|
|
is_wild_card_elems_clause =
|
|
is_wild_card_elems_clause && !pattern_has_conditions(element, data_types);
|
|
}
|
|
|
|
if is_wild_card_elems_clause {
|
|
if wild_card_clause_elems < elements.len() + usize::from(tail.is_none()) {
|
|
wild_card_clause_elems += 1;
|
|
}
|
|
|
|
if clause.guard.is_none() && tail.is_some() && !elements.is_empty() {
|
|
last_clause_index = index;
|
|
last_clause_set = true;
|
|
}
|
|
}
|
|
} else if let Pattern::Var { .. } | Pattern::Discard { .. } = &clause.pattern {
|
|
if clause.guard.is_none() {
|
|
last_clause_set = true;
|
|
last_clause_index = index;
|
|
}
|
|
} else {
|
|
unreachable!("Found a clause that is not a list or var or discard");
|
|
}
|
|
|
|
// If the last condition doesn't have a catch all or tail then add a catch all with a todo
|
|
if index == sorted_clauses.len() - 1 {
|
|
if let Pattern::List { tail: None, .. } = &clause.pattern {
|
|
final_clauses.push(TypedClause {
|
|
location: Span::empty(),
|
|
pattern: Pattern::Discard {
|
|
name: "_".to_string(),
|
|
location: Span::empty(),
|
|
},
|
|
guard: None,
|
|
then: plug_in_then(index + 1, last_clause),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Encountered a tail so stop there with that as last clause
|
|
if last_clause_set {
|
|
for _ in 0..(sorted_clauses.len() - 1 - last_clause_index) {
|
|
final_clauses.pop();
|
|
}
|
|
}
|
|
|
|
// insert hole fillers into clauses
|
|
for (index, clause) in holes_to_fill.into_iter().rev() {
|
|
final_clauses.insert(index, clause);
|
|
}
|
|
assert!(final_clauses.len() > 1);
|
|
|
|
final_clauses
|
|
}
|
|
|
|
pub fn find_list_clause_or_default_first(clauses: &[TypedClause]) -> &TypedClause {
|
|
assert!(!clauses.is_empty());
|
|
|
|
clauses
|
|
.iter()
|
|
.find(|clause| match &clause.pattern {
|
|
Pattern::List { .. } => true,
|
|
Pattern::Assign { pattern, .. } if matches!(&**pattern, Pattern::List { .. }) => true,
|
|
_ => false,
|
|
})
|
|
.unwrap_or(&clauses[0])
|
|
}
|
|
|
|
pub fn convert_data_to_type(term: Term<Name>, field_type: &Rc<Type>) -> Term<Name> {
|
|
if field_type.is_int() {
|
|
Term::un_i_data().apply(term)
|
|
} else if field_type.is_bytearray() {
|
|
Term::un_b_data().apply(term)
|
|
} else if field_type.is_void() {
|
|
Term::equals_integer()
|
|
.apply(Term::integer(0.into()))
|
|
.apply(Term::fst_pair().apply(Term::unconstr_data().apply(term)))
|
|
.delayed_if_else(Term::unit(), Term::Error)
|
|
} else if field_type.is_map() {
|
|
Term::unmap_data().apply(term)
|
|
} else if field_type.is_string() {
|
|
Term::Builtin(DefaultFunction::DecodeUtf8).apply(Term::un_b_data().apply(term))
|
|
} else if field_type.is_tuple() && matches!(field_type.get_uplc_type(), UplcType::Pair(_, _)) {
|
|
Term::mk_pair_data()
|
|
.apply(Term::head_list().apply(Term::var("__list_data")))
|
|
.apply(Term::head_list().apply(Term::var("__tail")))
|
|
.lambda("__tail")
|
|
.apply(Term::tail_list().apply(Term::var("__list_data")))
|
|
.lambda("__list_data")
|
|
.apply(Term::unlist_data().apply(term))
|
|
} else if field_type.is_list() || field_type.is_tuple() {
|
|
Term::unlist_data().apply(term)
|
|
} else if field_type.is_bool() {
|
|
Term::equals_integer()
|
|
.apply(Term::integer(1.into()))
|
|
.apply(Term::fst_pair().apply(Term::unconstr_data().apply(term)))
|
|
} else {
|
|
term
|
|
}
|
|
}
|
|
|
|
pub fn convert_constants_to_data(constants: Vec<Rc<UplcConstant>>) -> Vec<UplcConstant> {
|
|
let mut new_constants = vec![];
|
|
for constant in constants {
|
|
let constant = match constant.as_ref() {
|
|
UplcConstant::Integer(i) => UplcConstant::Data(PlutusData::BigInt(to_pallas_bigint(i))),
|
|
UplcConstant::ByteString(b) => {
|
|
UplcConstant::Data(PlutusData::BoundedBytes(b.clone().try_into().unwrap()))
|
|
}
|
|
UplcConstant::String(s) => UplcConstant::Data(PlutusData::BoundedBytes(
|
|
s.as_bytes().to_vec().try_into().unwrap(),
|
|
)),
|
|
|
|
UplcConstant::Bool(b) => UplcConstant::Data(PlutusData::Constr(Constr {
|
|
tag: convert_constr_to_tag((*b).into()).unwrap_or(ANY_TAG),
|
|
any_constructor: convert_constr_to_tag((*b).into())
|
|
.map_or(Some((*b).into()), |_| None),
|
|
fields: vec![],
|
|
})),
|
|
UplcConstant::ProtoList(list_type, constants) => {
|
|
if matches!(list_type, UplcType::Pair(_, _)) {
|
|
let inner_constants = constants
|
|
.iter()
|
|
.cloned()
|
|
.map(|pair| match pair {
|
|
UplcConstant::ProtoPair(_, _, left, right) => {
|
|
let inner_constants = vec![left, right];
|
|
let inner_constants = convert_constants_to_data(inner_constants)
|
|
.into_iter()
|
|
.map(|constant| match constant {
|
|
UplcConstant::Data(d) => d,
|
|
_ => todo!(),
|
|
})
|
|
.collect_vec();
|
|
(inner_constants[0].clone(), inner_constants[1].clone())
|
|
}
|
|
_ => unreachable!(),
|
|
})
|
|
.collect_vec();
|
|
|
|
UplcConstant::Data(PlutusData::Map(KeyValuePairs::Def(inner_constants)))
|
|
} else {
|
|
let inner_constants =
|
|
convert_constants_to_data(constants.iter().cloned().map(Rc::new).collect())
|
|
.into_iter()
|
|
.map(|constant| match constant {
|
|
UplcConstant::Data(d) => d,
|
|
_ => todo!(),
|
|
})
|
|
.collect_vec();
|
|
|
|
UplcConstant::Data(PlutusData::Array(inner_constants))
|
|
}
|
|
}
|
|
UplcConstant::ProtoPair(_, _, left, right) => {
|
|
let inner_constants = vec![left.clone(), right.clone()];
|
|
let inner_constants = convert_constants_to_data(inner_constants)
|
|
.into_iter()
|
|
.map(|constant| match constant {
|
|
UplcConstant::Data(d) => d,
|
|
_ => todo!(),
|
|
})
|
|
.collect_vec();
|
|
|
|
UplcConstant::Data(PlutusData::Array(vec![
|
|
inner_constants[0].clone(),
|
|
inner_constants[1].clone(),
|
|
]))
|
|
}
|
|
d @ UplcConstant::Data(_) => d.clone(),
|
|
UplcConstant::Unit => UplcConstant::Data(PlutusData::Constr(Constr {
|
|
tag: convert_constr_to_tag(0).unwrap(),
|
|
any_constructor: None,
|
|
fields: vec![],
|
|
})),
|
|
};
|
|
new_constants.push(constant);
|
|
}
|
|
new_constants
|
|
}
|
|
|
|
pub fn convert_type_to_data(term: Term<Name>, field_type: &Rc<Type>) -> Term<Name> {
|
|
if field_type.is_bytearray() {
|
|
Term::b_data().apply(term)
|
|
} else if field_type.is_int() {
|
|
Term::i_data().apply(term)
|
|
} else if field_type.is_void() {
|
|
term.choose_unit(Term::Constant(
|
|
UplcConstant::Data(PlutusData::Constr(Constr {
|
|
tag: convert_constr_to_tag(0).unwrap(),
|
|
any_constructor: None,
|
|
fields: vec![],
|
|
}))
|
|
.into(),
|
|
))
|
|
} else if field_type.is_map() {
|
|
Term::map_data().apply(term)
|
|
} else if field_type.is_string() {
|
|
Term::b_data().apply(Term::Builtin(DefaultFunction::EncodeUtf8).apply(term))
|
|
} else if field_type.is_tuple() && matches!(field_type.get_uplc_type(), UplcType::Pair(_, _)) {
|
|
Term::list_data()
|
|
.apply(
|
|
Term::mk_cons()
|
|
.apply(Term::fst_pair().apply(Term::var("__pair")))
|
|
.apply(
|
|
Term::mk_cons()
|
|
.apply(Term::snd_pair().apply(Term::var("__pair")))
|
|
.apply(Term::empty_list()),
|
|
),
|
|
)
|
|
.lambda("__pair")
|
|
.apply(term)
|
|
} else if field_type.is_list() || field_type.is_tuple() {
|
|
Term::list_data().apply(term)
|
|
} else if field_type.is_bool() {
|
|
term.if_else(
|
|
Term::Constant(
|
|
UplcConstant::Data(PlutusData::Constr(Constr {
|
|
tag: convert_constr_to_tag(1).unwrap(),
|
|
any_constructor: None,
|
|
fields: vec![],
|
|
}))
|
|
.into(),
|
|
),
|
|
Term::Constant(
|
|
UplcConstant::Data(PlutusData::Constr(Constr {
|
|
tag: convert_constr_to_tag(0).unwrap(),
|
|
any_constructor: None,
|
|
fields: vec![],
|
|
}))
|
|
.into(),
|
|
),
|
|
)
|
|
} else {
|
|
term
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn list_access_to_uplc(
|
|
names_types: &[(String, Rc<Type>)],
|
|
id_list: &[u64],
|
|
tail: bool,
|
|
current_index: usize,
|
|
term: Term<Name>,
|
|
check_last_item: bool,
|
|
is_list_accessor: bool,
|
|
error_term: Term<Name>,
|
|
) -> Term<Name> {
|
|
if let Some(((first, current_tipo), names_types)) = names_types.split_first() {
|
|
let head_list =
|
|
if matches!(current_tipo.get_uplc_type(), UplcType::Pair(_, _)) && is_list_accessor {
|
|
Term::head_list().apply(Term::var(format!(
|
|
"tail_index_{}_{}",
|
|
current_index, id_list[current_index]
|
|
)))
|
|
} else {
|
|
convert_data_to_type(
|
|
Term::head_list().apply(Term::var(format!(
|
|
"tail_index_{}_{}",
|
|
current_index, id_list[current_index]
|
|
))),
|
|
¤t_tipo.to_owned(),
|
|
)
|
|
};
|
|
|
|
if names_types.len() == 1 && tail {
|
|
if first == "_" && names_types[0].0 == "_" {
|
|
term.lambda("_")
|
|
} else if first == "_" {
|
|
term.lambda(&names_types[0].0)
|
|
.apply(Term::tail_list().apply(Term::var(format!(
|
|
"tail_index_{}_{}",
|
|
current_index, id_list[current_index]
|
|
))))
|
|
.lambda(format!(
|
|
"tail_index_{}_{}",
|
|
current_index, id_list[current_index]
|
|
))
|
|
} else if names_types[0].0 == "_" {
|
|
term.lambda(first).apply(head_list).lambda(format!(
|
|
"tail_index_{}_{}",
|
|
current_index, id_list[current_index]
|
|
))
|
|
} else {
|
|
term.lambda(&names_types[0].0)
|
|
.apply(Term::tail_list().apply(Term::var(format!(
|
|
"tail_index_{}_{}",
|
|
current_index, id_list[current_index]
|
|
))))
|
|
.lambda(first)
|
|
.apply(head_list)
|
|
.lambda(format!(
|
|
"tail_index_{}_{}",
|
|
current_index, id_list[current_index]
|
|
))
|
|
}
|
|
} else if names_types.is_empty() {
|
|
if first == "_" {
|
|
if check_last_item {
|
|
Term::tail_list()
|
|
.apply(Term::var(format!(
|
|
"tail_index_{}_{}",
|
|
current_index, id_list[current_index]
|
|
)))
|
|
.delayed_choose_list(term, error_term)
|
|
} else {
|
|
term
|
|
}
|
|
.lambda(if check_last_item {
|
|
format!("tail_index_{}_{}", current_index, id_list[current_index])
|
|
} else {
|
|
"_".to_string()
|
|
})
|
|
} else {
|
|
if check_last_item {
|
|
Term::tail_list()
|
|
.apply(Term::var(format!(
|
|
"tail_index_{}_{}",
|
|
current_index, id_list[current_index]
|
|
)))
|
|
.delayed_choose_list(term, error_term)
|
|
} else {
|
|
term
|
|
}
|
|
.lambda(first.clone())
|
|
.apply(head_list)
|
|
.lambda(format!(
|
|
"tail_index_{}_{}",
|
|
current_index, id_list[current_index]
|
|
))
|
|
}
|
|
} else if first == "_" {
|
|
let mut list_access_inner = list_access_to_uplc(
|
|
names_types,
|
|
id_list,
|
|
tail,
|
|
current_index + 1,
|
|
term,
|
|
check_last_item,
|
|
is_list_accessor,
|
|
error_term,
|
|
);
|
|
|
|
list_access_inner = match &list_access_inner {
|
|
Term::Lambda {
|
|
parameter_name,
|
|
body,
|
|
} => {
|
|
if ¶meter_name.text == "_" {
|
|
body.as_ref().clone()
|
|
} else {
|
|
list_access_inner
|
|
.apply(Term::tail_list().apply(Term::var(format!(
|
|
"tail_index_{}_{}",
|
|
current_index, id_list[current_index]
|
|
))))
|
|
.lambda(format!(
|
|
"tail_index_{}_{}",
|
|
current_index, id_list[current_index]
|
|
))
|
|
}
|
|
}
|
|
_ => list_access_inner,
|
|
};
|
|
|
|
match &list_access_inner {
|
|
Term::Lambda { .. } => list_access_inner,
|
|
_ => list_access_inner.lambda("_"),
|
|
}
|
|
} else {
|
|
let mut list_access_inner = list_access_to_uplc(
|
|
names_types,
|
|
id_list,
|
|
tail,
|
|
current_index + 1,
|
|
term,
|
|
check_last_item,
|
|
is_list_accessor,
|
|
error_term,
|
|
);
|
|
|
|
list_access_inner = match &list_access_inner {
|
|
Term::Lambda {
|
|
parameter_name,
|
|
body,
|
|
} => {
|
|
if ¶meter_name.text == "_" {
|
|
body.as_ref()
|
|
.clone()
|
|
.lambda(first.clone())
|
|
.apply(head_list)
|
|
.lambda(format!(
|
|
"tail_index_{}_{}",
|
|
current_index, id_list[current_index]
|
|
))
|
|
} else {
|
|
list_access_inner
|
|
.apply(Term::tail_list().apply(Term::var(format!(
|
|
"tail_index_{}_{}",
|
|
current_index, id_list[current_index]
|
|
))))
|
|
.lambda(first.clone())
|
|
.apply(head_list)
|
|
.lambda(format!(
|
|
"tail_index_{}_{}",
|
|
current_index, id_list[current_index]
|
|
))
|
|
}
|
|
}
|
|
_ => list_access_inner
|
|
.lambda(first.clone())
|
|
.apply(head_list)
|
|
.lambda(format!(
|
|
"tail_index_{}_{}",
|
|
current_index, id_list[current_index]
|
|
)),
|
|
};
|
|
list_access_inner
|
|
}
|
|
} else {
|
|
term
|
|
}
|
|
}
|
|
|
|
pub fn apply_builtin_forces(mut term: Term<Name>, force_count: u32) -> Term<Name> {
|
|
for _ in 0..force_count {
|
|
term = term.force();
|
|
}
|
|
term
|
|
}
|
|
|
|
pub fn undata_builtin(
|
|
func: &DefaultFunction,
|
|
count: usize,
|
|
tipo: &Rc<Type>,
|
|
args: Vec<Term<Name>>,
|
|
) -> Term<Name> {
|
|
let mut term: Term<Name> = (*func).into();
|
|
|
|
term = apply_builtin_forces(term, func.force_count());
|
|
|
|
for arg in args {
|
|
term = term.apply(arg);
|
|
}
|
|
|
|
let temp_var = "__item_x";
|
|
|
|
if count == 0 {
|
|
term = term.apply(Term::var(temp_var));
|
|
}
|
|
|
|
term = convert_data_to_type(term, tipo);
|
|
|
|
if count == 0 {
|
|
term = term.lambda(temp_var);
|
|
}
|
|
term
|
|
}
|
|
|
|
pub fn to_data_builtin(
|
|
func: &DefaultFunction,
|
|
count: usize,
|
|
tipo: &Rc<Type>,
|
|
mut args: Vec<Term<Name>>,
|
|
) -> Term<Name> {
|
|
let mut term: Term<Name> = (*func).into();
|
|
|
|
term = apply_builtin_forces(term, func.force_count());
|
|
|
|
if count == 0 {
|
|
assert!(args.is_empty());
|
|
|
|
for arg_index in 0..func.arity() {
|
|
let temp_var = format!("__item_index_{}", arg_index);
|
|
|
|
args.push(Term::var(temp_var))
|
|
}
|
|
}
|
|
|
|
for (index, arg) in args.into_iter().enumerate() {
|
|
if index == 0 || matches!(func, DefaultFunction::MkPairData) {
|
|
term = term.apply(convert_type_to_data(arg, tipo));
|
|
} else {
|
|
term = term.apply(arg);
|
|
}
|
|
}
|
|
|
|
if count == 0 {
|
|
for arg_index in (0..func.arity()).rev() {
|
|
let temp_var = format!("__item_index_{}", arg_index);
|
|
term = term.lambda(temp_var);
|
|
}
|
|
}
|
|
|
|
term
|
|
}
|
|
|
|
pub fn special_case_builtin(
|
|
func: &DefaultFunction,
|
|
count: usize,
|
|
mut args: Vec<Term<Name>>,
|
|
) -> Term<Name> {
|
|
match func {
|
|
DefaultFunction::IfThenElse
|
|
| DefaultFunction::ChooseList
|
|
| DefaultFunction::ChooseData
|
|
| DefaultFunction::Trace => {
|
|
let mut term: Term<Name> = (*func).into();
|
|
|
|
term = apply_builtin_forces(term, func.force_count());
|
|
|
|
if count == 0 {
|
|
assert!(args.is_empty());
|
|
|
|
for arg_index in 0..func.arity() {
|
|
let temp_var = format!("__item_index_{}", arg_index);
|
|
|
|
args.push(Term::var(temp_var))
|
|
}
|
|
}
|
|
|
|
for (index, arg) in args.into_iter().enumerate() {
|
|
if index == 0 {
|
|
term = term.apply(arg);
|
|
} else {
|
|
term = term.apply(arg.delay());
|
|
}
|
|
}
|
|
|
|
term = term.force();
|
|
|
|
if count == 0 {
|
|
for arg_index in (0..func.arity()).rev() {
|
|
let temp_var = format!("__item_index_{}", arg_index);
|
|
term = term.lambda(temp_var);
|
|
}
|
|
}
|
|
|
|
term
|
|
}
|
|
DefaultFunction::ChooseUnit => {
|
|
if count == 0 {
|
|
unimplemented!("Honestly, why are you doing this?")
|
|
} else {
|
|
let term = args.pop().unwrap();
|
|
let unit = args.pop().unwrap();
|
|
|
|
term.lambda("_").apply(unit)
|
|
}
|
|
}
|
|
DefaultFunction::UnConstrData => {
|
|
let mut term: Term<Name> = (*func).into();
|
|
|
|
let temp_tuple = "__unconstr_tuple";
|
|
|
|
for arg in args {
|
|
term = term.apply(arg);
|
|
}
|
|
|
|
let temp_var = "__item_x";
|
|
|
|
if count == 0 {
|
|
term = term.apply(Term::var(temp_var));
|
|
}
|
|
|
|
term = Term::mk_pair_data()
|
|
.apply(Term::i_data().apply(Term::fst_pair().apply(Term::var(temp_tuple))))
|
|
.apply(Term::list_data().apply(Term::snd_pair().apply(Term::var(temp_tuple))))
|
|
.lambda(temp_tuple)
|
|
.apply(term);
|
|
|
|
if count == 0 {
|
|
term = term.lambda(temp_var);
|
|
}
|
|
|
|
term
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
pub fn wrap_as_multi_validator(
|
|
spend: Term<Name>,
|
|
mint: Term<Name>,
|
|
trace: bool,
|
|
spend_name: String,
|
|
mint_name: String,
|
|
) -> Term<Name> {
|
|
if trace {
|
|
let trace_string = format!(
|
|
"Incorrect redeemer type for validator {}.
|
|
Double check you have wrapped the redeemer type as specified in your plutus.json",
|
|
spend_name
|
|
);
|
|
|
|
let error_term = Term::Error.trace(Term::var("__incorrect_second_arg_type"));
|
|
|
|
Term::var("__second_arg")
|
|
.delayed_choose_data(
|
|
Term::equals_integer()
|
|
.apply(Term::integer(0.into()))
|
|
.apply(Term::var(CONSTR_INDEX_EXPOSER).apply(Term::var("__second_arg")))
|
|
.delayed_if_else(
|
|
mint.apply(Term::var("__first_arg"))
|
|
.apply(Term::var("__second_arg"))
|
|
.trace(Term::string(format!(
|
|
"Running 2 arg validator {}",
|
|
mint_name
|
|
))),
|
|
spend
|
|
.apply(Term::var("__first_arg"))
|
|
.apply(Term::head_list().apply(
|
|
Term::var(CONSTR_FIELDS_EXPOSER).apply(Term::var("__second_arg")),
|
|
))
|
|
.trace(Term::string(format!(
|
|
"Running 3 arg validator {}",
|
|
spend_name
|
|
))),
|
|
),
|
|
error_term.clone(),
|
|
error_term.clone(),
|
|
error_term.clone(),
|
|
error_term,
|
|
)
|
|
.lambda("__incorrect_second_arg_type")
|
|
.apply(Term::string(trace_string))
|
|
.lambda("__second_arg")
|
|
.lambda("__first_arg")
|
|
} else {
|
|
Term::equals_integer()
|
|
.apply(Term::integer(0.into()))
|
|
.apply(Term::var(CONSTR_INDEX_EXPOSER).apply(Term::var("__second_arg")))
|
|
.delayed_if_else(
|
|
mint.apply(Term::var("__first_arg"))
|
|
.apply(Term::var("__second_arg")),
|
|
spend.apply(Term::var("__first_arg")).apply(
|
|
Term::head_list()
|
|
.apply(Term::var(CONSTR_FIELDS_EXPOSER).apply(Term::var("__second_arg"))),
|
|
),
|
|
)
|
|
.lambda("__second_arg")
|
|
.lambda("__first_arg")
|
|
}
|
|
}
|
|
|
|
/// If the pattern is a list the return the number of elements and if it has a tail
|
|
/// Otherwise return None
|
|
pub fn get_list_elements_len_and_tail(
|
|
pattern: &Pattern<PatternConstructor, Rc<Type>>,
|
|
) -> Option<(usize, bool)> {
|
|
if let Pattern::List { elements, tail, .. } = &pattern {
|
|
Some((elements.len(), tail.is_some()))
|
|
} else if let Pattern::Assign { pattern, .. } = &pattern {
|
|
if let Pattern::List { elements, tail, .. } = pattern.as_ref() {
|
|
Some((elements.len(), tail.is_some()))
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn cast_validator_args(term: Term<Name>, arguments: &[TypedArg]) -> Term<Name> {
|
|
let mut term = term;
|
|
for arg in arguments.iter().rev() {
|
|
if !matches!(arg.tipo.get_uplc_type(), UplcType::Data) {
|
|
term = term
|
|
.lambda(arg.arg_name.get_variable_name().unwrap_or("_"))
|
|
.apply(convert_data_to_type(
|
|
Term::var(arg.arg_name.get_variable_name().unwrap_or("_")),
|
|
&arg.tipo,
|
|
));
|
|
}
|
|
|
|
term = term.lambda(arg.arg_name.get_variable_name().unwrap_or("_"))
|
|
}
|
|
term
|
|
}
|
|
|
|
pub fn wrap_validator_condition(air_tree: AirTree, trace: bool) -> AirTree {
|
|
let success_branch = vec![(air_tree, AirTree::void())];
|
|
let otherwise = if trace {
|
|
AirTree::trace(
|
|
AirTree::string("Validator returned false"),
|
|
void(),
|
|
AirTree::error(void(), true),
|
|
)
|
|
} else {
|
|
AirTree::error(void(), true)
|
|
};
|
|
|
|
AirTree::if_branches(success_branch, void(), otherwise)
|
|
}
|
|
|
|
pub fn extract_constant(term: &Term<Name>) -> Option<Rc<UplcConstant>> {
|
|
let mut constant = None;
|
|
|
|
if let Term::Constant(c) = term {
|
|
constant = Some(c.clone());
|
|
} else if let Term::Apply { function, argument } = term {
|
|
if let Term::Constant(c) = argument.as_ref() {
|
|
if let Term::Builtin(b) = function.as_ref() {
|
|
if matches!(
|
|
b,
|
|
DefaultFunction::BData
|
|
| DefaultFunction::IData
|
|
| DefaultFunction::MapData
|
|
| DefaultFunction::ListData
|
|
) {
|
|
constant = Some(c.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
constant
|
|
}
|