1712 lines
61 KiB
Rust
1712 lines
61 KiB
Rust
use super::{
|
|
air::ExpectLevel,
|
|
tree::{AirMsg, AirTree, TreePath},
|
|
};
|
|
use crate::{
|
|
ast::{
|
|
Constant, DataTypeKey, FunctionAccessKey, Pattern, Span, TraceLevel, TypedArg,
|
|
TypedAssignmentKind, TypedClause, TypedDataType, TypedPattern,
|
|
},
|
|
builtins::{data, function, int, list, void},
|
|
expr::TypedExpr,
|
|
line_numbers::{LineColumn, LineNumbers},
|
|
tipo::{
|
|
check_replaceable_opaque_type, convert_opaque_type, find_and_replace_generics,
|
|
lookup_data_type_by_tipo, PatternConstructor, Type, ValueConstructor,
|
|
ValueConstructorVariant,
|
|
},
|
|
};
|
|
use indexmap::{IndexMap, IndexSet};
|
|
use itertools::{Itertools, Position};
|
|
use std::{collections::HashMap, ops::Deref, rc::Rc};
|
|
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, Compressable, ANY_TAG},
|
|
value::to_pallas_bigint,
|
|
},
|
|
Constr, KeyValuePairs, PlutusData,
|
|
};
|
|
|
|
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)]
|
|
pub struct AssignmentProperties {
|
|
pub value_type: Rc<Type>,
|
|
pub kind: TypedAssignmentKind,
|
|
pub remove_unused: bool,
|
|
pub full_check: bool,
|
|
pub otherwise: Option<AirTree>,
|
|
}
|
|
|
|
#[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)>,
|
|
},
|
|
PairClause,
|
|
}
|
|
|
|
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 if t.is_pair() {
|
|
ClauseProperties {
|
|
clause_var_name: constr_var,
|
|
complex_clause: false,
|
|
original_subject_name: subject_name,
|
|
needs_constr_var: false,
|
|
final_clause: false,
|
|
specific_clause: SpecificClause::PairClause,
|
|
}
|
|
} 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 if t.is_pair() {
|
|
ClauseProperties {
|
|
clause_var_name: constr_var,
|
|
complex_clause: false,
|
|
original_subject_name: subject_name,
|
|
needs_constr_var: false,
|
|
final_clause,
|
|
specific_clause: SpecificClause::PairClause,
|
|
}
|
|
} 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>, Rc<Type>)>,
|
|
}
|
|
|
|
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"),
|
|
function(vec![data()], list(data())),
|
|
),
|
|
);
|
|
|
|
key_to_func.insert(
|
|
CONSTR_INDEX_EXPOSER.to_string(),
|
|
(
|
|
Term::fst_pair()
|
|
.apply(Term::unconstr_data().apply(Term::var("__constr_var")))
|
|
.lambda("__constr_var"),
|
|
function(vec![data()], int()),
|
|
),
|
|
);
|
|
|
|
CodeGenSpecialFuncs {
|
|
used_funcs: vec![],
|
|
key_to_func,
|
|
}
|
|
}
|
|
|
|
pub fn use_function_tree(&mut self, func_name: String) -> AirTree {
|
|
if !self.used_funcs.contains(&func_name) {
|
|
self.used_funcs.push(func_name.to_string());
|
|
}
|
|
|
|
let tipo = self.key_to_func.get(&func_name).unwrap().1.clone();
|
|
|
|
AirTree::local_var(func_name, tipo)
|
|
}
|
|
|
|
pub fn use_function_msg(&mut self, func_name: String) -> AirMsg {
|
|
if !self.used_funcs.contains(&func_name) {
|
|
self.used_funcs.push(func_name.to_string());
|
|
}
|
|
|
|
AirMsg::LocalVar(func_name)
|
|
}
|
|
|
|
pub fn use_function_uplc(&mut self, func_name: String) -> String {
|
|
if !self.used_funcs.contains(&func_name) {
|
|
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].0.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
|
|
}
|
|
|
|
pub fn insert_new_function(
|
|
&mut self,
|
|
func_name: String,
|
|
function: Term<Name>,
|
|
function_type: Rc<Type>,
|
|
) {
|
|
if !self.key_to_func.contains_key(&func_name) {
|
|
self.key_to_func
|
|
.insert(func_name, (function, function_type));
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for CodeGenSpecialFuncs {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
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()),
|
|
Constant::CurvePoint { point, .. } => AirTree::curve(*point.as_ref()),
|
|
}
|
|
}
|
|
|
|
pub fn get_generic_variant_name(t: &Rc<Type>) -> String {
|
|
let uplc_type = t.get_uplc_type();
|
|
|
|
match uplc_type {
|
|
Some(UplcType::Bool) => "_bool".to_string(),
|
|
Some(UplcType::Integer) => "_int".to_string(),
|
|
Some(UplcType::String) => "_string".to_string(),
|
|
Some(UplcType::ByteString) => "_bytearray".to_string(),
|
|
Some(UplcType::Unit) => "_void".to_string(),
|
|
Some(UplcType::List(_)) if t.is_map() => "_map".to_string(),
|
|
Some(UplcType::List(_)) => "_list".to_string(),
|
|
Some(UplcType::Pair(_, _)) => "_pair".to_string(),
|
|
Some(UplcType::Bls12_381G1Element) => "_bls381_12_g1".to_string(),
|
|
Some(UplcType::Bls12_381G2Element) => "_bls381_12_g2".to_string(),
|
|
Some(UplcType::Bls12_381MlResult) => "_ml_result".to_string(),
|
|
None if t.is_unbound() => "_unbound".to_string(),
|
|
None if t.is_generic() => {
|
|
unreachable!("FOUND A POLYMORPHIC TYPE. EXPECTED MONOMORPHIC TYPE")
|
|
}
|
|
None | Some(UplcType::Data) => "_data".to_string(),
|
|
}
|
|
}
|
|
|
|
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::Constr { tipo, args, .. } = air_tree {
|
|
if check_replaceable_opaque_type(tipo, data_types) {
|
|
let arg = args.pop().unwrap();
|
|
|
|
match arg {
|
|
AirTree::CastToData { value, .. } => *air_tree = *value,
|
|
_ => *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, true);
|
|
}
|
|
}
|
|
|
|
/// 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::Let { name, .. } => vec![name.clone()],
|
|
AirTree::SoftCastLet { name, .. } => vec![name.clone()],
|
|
AirTree::TupleGuard { indices, .. } | AirTree::TupleClause { indices, .. } => {
|
|
indices.iter().map(|(_, name)| name.clone()).collect()
|
|
}
|
|
AirTree::PairGuard {
|
|
fst_name, snd_name, ..
|
|
} => fst_name
|
|
.into_iter()
|
|
.cloned()
|
|
.chain(snd_name.into_iter().cloned())
|
|
.collect_vec(),
|
|
AirTree::PairAccessor { fst, snd, .. } => fst
|
|
.into_iter()
|
|
.cloned()
|
|
.chain(snd.into_iter().cloned())
|
|
.collect_vec(),
|
|
AirTree::PairClause {
|
|
fst_name, snd_name, ..
|
|
} => fst_name
|
|
.into_iter()
|
|
.cloned()
|
|
.chain(snd_name.into_iter().cloned())
|
|
.collect_vec(),
|
|
AirTree::Fn { params, .. } => params.to_vec(),
|
|
AirTree::ListAccessor { names, .. } => names.clone(),
|
|
AirTree::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::TupleAccessor { names, .. } => names.clone(),
|
|
AirTree::FieldsExpose { indices, .. } => {
|
|
indices.iter().map(|(_, name, _)| name.clone()).collect()
|
|
}
|
|
AirTree::When { subject_name, .. } => vec![subject_name.clone()],
|
|
_ => 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::Call { func, args, .. } = air_tree {
|
|
if let AirTree::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::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::Call { func, args, .. } = air_tree {
|
|
if let AirTree::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::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),
|
|
false,
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
true,
|
|
);
|
|
}
|
|
|
|
pub fn pattern_has_conditions(
|
|
pattern: &TypedPattern,
|
|
data_types: &IndexMap<&DataTypeKey, &TypedDataType>,
|
|
) -> bool {
|
|
match pattern {
|
|
Pattern::List { .. } | Pattern::Int { .. } | Pattern::ByteArray { .. } => true,
|
|
Pattern::Tuple { elems, .. } => elems
|
|
.iter()
|
|
.any(|elem| pattern_has_conditions(elem, data_types)),
|
|
Pattern::Pair { fst, snd, .. } => {
|
|
pattern_has_conditions(fst, data_types) || pattern_has_conditions(snd, data_types)
|
|
}
|
|
Pattern::Constructor {
|
|
arguments, tipo, ..
|
|
} => {
|
|
let data_type = lookup_data_type_by_tipo(data_types, tipo)
|
|
.unwrap_or_else(|| panic!("Data type not found: {:#?}", tipo));
|
|
|
|
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()))
|
|
}
|
|
_ => Some(100000),
|
|
};
|
|
|
|
let clause2_len = match clause_pattern2 {
|
|
Pattern::List { elements, tail, .. } => {
|
|
Some(elements.len() + usize::from(tail.is_some()))
|
|
}
|
|
_ => Some(100001),
|
|
};
|
|
|
|
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| 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,
|
|
}),
|
|
}
|
|
}
|
|
};
|
|
|
|
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(),
|
|
},
|
|
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,
|
|
},
|
|
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 = true;
|
|
|
|
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 tail.is_some() && !elements.is_empty() {
|
|
last_clause_index = index;
|
|
last_clause_set = true;
|
|
}
|
|
}
|
|
} else if let Pattern::Var { .. } | Pattern::Discard { .. } = &clause.pattern {
|
|
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(),
|
|
},
|
|
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 known_data_to_type(term: Term<Name>, field_type: &Type) -> Term<Name> {
|
|
let uplc_type = field_type.get_uplc_type();
|
|
|
|
match uplc_type {
|
|
Some(UplcType::Integer) => Term::un_i_data().apply(term),
|
|
Some(UplcType::ByteString) => Term::un_b_data().apply(term),
|
|
Some(UplcType::Bool) => Term::equals_integer()
|
|
.apply(Term::integer(1.into()))
|
|
.apply(Term::fst_pair().apply(Term::unconstr_data().apply(term))),
|
|
Some(UplcType::String) => Term::decode_utf8().apply(Term::un_b_data().apply(term)),
|
|
Some(UplcType::Unit) => Term::unit().lambda("_").apply(term),
|
|
Some(UplcType::List(_)) if field_type.is_map() => Term::unmap_data().apply(term),
|
|
Some(UplcType::List(_)) => Term::unlist_data().apply(term),
|
|
Some(UplcType::Pair(_, _)) => Term::mk_pair_data()
|
|
.apply(Term::head_list().apply(Term::var("__list_data")))
|
|
.apply(Term::head_list().apply(Term::tail_list().apply(Term::var("__list_data"))))
|
|
.lambda("__list_data")
|
|
.apply(Term::unlist_data().apply(term)),
|
|
|
|
Some(UplcType::Bls12_381G1Element) => {
|
|
Term::bls12_381_g1_uncompress().apply(Term::un_b_data().apply(term))
|
|
}
|
|
Some(UplcType::Bls12_381G2Element) => {
|
|
Term::bls12_381_g2_uncompress().apply(Term::un_b_data().apply(term))
|
|
}
|
|
Some(UplcType::Bls12_381MlResult) => panic!("ML Result not supported"),
|
|
Some(UplcType::Data) | None => term,
|
|
}
|
|
}
|
|
|
|
pub fn unknown_data_to_type(term: Term<Name>, field_type: &Type) -> Term<Name> {
|
|
let uplc_type = field_type.get_uplc_type();
|
|
|
|
match uplc_type {
|
|
Some(UplcType::Integer) => Term::un_i_data().apply(term),
|
|
Some(UplcType::ByteString) => Term::un_b_data().apply(term),
|
|
Some(UplcType::String) => Term::decode_utf8().apply(Term::un_b_data().apply(term)),
|
|
Some(UplcType::List(_)) if field_type.is_map() => Term::unmap_data().apply(term),
|
|
Some(UplcType::List(_)) => Term::unlist_data().apply(term),
|
|
|
|
Some(UplcType::Bls12_381G1Element) => {
|
|
Term::bls12_381_g1_uncompress().apply(Term::un_b_data().apply(term))
|
|
}
|
|
Some(UplcType::Bls12_381G2Element) => {
|
|
Term::bls12_381_g2_uncompress().apply(Term::un_b_data().apply(term))
|
|
}
|
|
Some(UplcType::Bls12_381MlResult) => panic!("ML Result not supported"),
|
|
|
|
Some(UplcType::Pair(_, _)) => Term::tail_list()
|
|
.apply(Term::tail_list().apply(Term::var("__list_data")))
|
|
.delayed_choose_list(
|
|
Term::mk_pair_data()
|
|
.apply(Term::head_list().apply(Term::var("__list_data")))
|
|
.apply(
|
|
Term::head_list().apply(Term::tail_list().apply(Term::var("__list_data"))),
|
|
),
|
|
Term::Error,
|
|
)
|
|
.lambda("__list_data")
|
|
.apply(Term::unlist_data().apply(term)),
|
|
Some(UplcType::Bool) => {
|
|
Term::unwrap_bool_or(term, |result| result.delay(), &Term::Error.delay())
|
|
}
|
|
Some(UplcType::Unit) => {
|
|
Term::unwrap_void_or(term, |result| result.delay(), &Term::Error.delay())
|
|
}
|
|
|
|
Some(UplcType::Data) | None => term,
|
|
}
|
|
}
|
|
|
|
/// Due to the nature of the types BLS12_381_G1Element and BLS12_381_G2Element and String coming from bytearray
|
|
/// We don't have error handling if the bytearray is not properly aligned to the type. Oh well lol
|
|
/// For BLS12_381_G1Element and BLS12_381_G2Element, hash to group exists so just adopt that.
|
|
pub fn softcast_data_to_type_otherwise(
|
|
value: Term<Name>,
|
|
name: &String,
|
|
field_type: &Type,
|
|
then: Term<Name>,
|
|
otherwise_delayed: Term<Name>,
|
|
) -> Term<Name> {
|
|
let uplc_type = field_type.get_uplc_type();
|
|
|
|
let just_then = then.clone();
|
|
|
|
let then_delayed = |v| then.lambda(name).apply(v).delay();
|
|
|
|
value.as_var("__val", |val| match uplc_type {
|
|
None => Term::choose_data_constr(val, then_delayed, &otherwise_delayed).force(),
|
|
|
|
Some(UplcType::Data) => just_then.lambda(name).apply(Term::Var(val)),
|
|
|
|
Some(UplcType::Bls12_381MlResult) => {
|
|
unreachable!("attempted to cast Data into Bls12_381MlResult ?!")
|
|
}
|
|
|
|
Some(UplcType::Integer) => {
|
|
Term::choose_data_integer(val, then_delayed, &otherwise_delayed).force()
|
|
}
|
|
|
|
Some(UplcType::ByteString) => {
|
|
Term::choose_data_bytearray(val, then_delayed, &otherwise_delayed).force()
|
|
}
|
|
|
|
Some(UplcType::String) => Term::choose_data_bytearray(
|
|
val,
|
|
|bytes| then_delayed(Term::decode_utf8().apply(bytes)),
|
|
&otherwise_delayed,
|
|
)
|
|
.force(),
|
|
|
|
Some(UplcType::List(_)) if field_type.is_map() => {
|
|
Term::choose_data_map(val, then_delayed, &otherwise_delayed).force()
|
|
}
|
|
|
|
Some(UplcType::List(_)) => {
|
|
Term::choose_data_list(val, then_delayed, &otherwise_delayed).force()
|
|
}
|
|
|
|
Some(UplcType::Bls12_381G1Element) => Term::choose_data_bytearray(
|
|
val,
|
|
|bytes| then_delayed(Term::bls12_381_g1_uncompress().apply(bytes)),
|
|
&otherwise_delayed,
|
|
)
|
|
.force(),
|
|
|
|
Some(UplcType::Bls12_381G2Element) => Term::choose_data_bytearray(
|
|
val,
|
|
|bytes| then_delayed(Term::bls12_381_g2_uncompress().apply(bytes)),
|
|
&otherwise_delayed,
|
|
)
|
|
.force(),
|
|
|
|
Some(UplcType::Pair(_, _)) => Term::choose_data_list(
|
|
val,
|
|
|list| list.unwrap_pair_or(then_delayed, &otherwise_delayed),
|
|
&otherwise_delayed,
|
|
)
|
|
.force(),
|
|
|
|
Some(UplcType::Bool) => Term::choose_data_constr(
|
|
val,
|
|
|constr| constr.unwrap_bool_or(then_delayed, &otherwise_delayed),
|
|
&otherwise_delayed,
|
|
)
|
|
.force(),
|
|
|
|
Some(UplcType::Unit) => Term::choose_data_constr(
|
|
val,
|
|
|constr| constr.unwrap_void_or(then_delayed, &otherwise_delayed),
|
|
&otherwise_delayed,
|
|
)
|
|
.force(),
|
|
})
|
|
}
|
|
|
|
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().into()))
|
|
}
|
|
UplcConstant::String(s) => {
|
|
UplcConstant::Data(PlutusData::BoundedBytes(s.as_bytes().to_vec().into()))
|
|
}
|
|
|
|
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![],
|
|
})),
|
|
UplcConstant::Bls12_381G1Element(b) => UplcConstant::Data(PlutusData::BoundedBytes(
|
|
b.deref().clone().compress().into(),
|
|
)),
|
|
UplcConstant::Bls12_381G2Element(b) => UplcConstant::Data(PlutusData::BoundedBytes(
|
|
b.deref().clone().compress().into(),
|
|
)),
|
|
UplcConstant::Bls12_381MlResult(_) => panic!("Bls12_381MlResult not supported"),
|
|
};
|
|
new_constants.push(constant);
|
|
}
|
|
new_constants
|
|
}
|
|
|
|
pub fn convert_type_to_data(term: Term<Name>, field_type: &Rc<Type>) -> Term<Name> {
|
|
let uplc_type = field_type.get_uplc_type();
|
|
|
|
match uplc_type {
|
|
Some(UplcType::Integer) => Term::i_data().apply(term),
|
|
Some(UplcType::String) => Term::b_data().apply(Term::encode_utf8().apply(term)),
|
|
Some(UplcType::ByteString) => Term::b_data().apply(term),
|
|
Some(UplcType::List(_)) if field_type.is_map() => Term::map_data().apply(term),
|
|
Some(UplcType::List(_)) => Term::list_data().apply(term),
|
|
|
|
Some(UplcType::Bls12_381G1Element) => {
|
|
Term::b_data().apply(Term::bls12_381_g1_compress().apply(term))
|
|
}
|
|
Some(UplcType::Bls12_381G2Element) => {
|
|
Term::b_data().apply(Term::bls12_381_g2_compress().apply(term))
|
|
}
|
|
Some(UplcType::Bls12_381MlResult) => panic!("ML Result not supported"),
|
|
Some(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),
|
|
Some(UplcType::Unit) => Term::Constant(
|
|
UplcConstant::Data(PlutusData::Constr(Constr {
|
|
tag: convert_constr_to_tag(0).unwrap(),
|
|
any_constructor: None,
|
|
fields: vec![],
|
|
}))
|
|
.into(),
|
|
)
|
|
.lambda("_")
|
|
.apply(term),
|
|
Some(UplcType::Bool) => term.if_then_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(),
|
|
),
|
|
),
|
|
|
|
Some(UplcType::Data) | None => term,
|
|
}
|
|
}
|
|
|
|
pub fn list_access_to_uplc(
|
|
names_types_ids: &[(String, Rc<Type>, u64)],
|
|
tail_present: bool,
|
|
term: Term<Name>,
|
|
is_list_accessor: bool,
|
|
expect_level: ExpectLevel,
|
|
otherwise_delayed: Term<Name>,
|
|
) -> Term<Name> {
|
|
let names_len = names_types_ids.len();
|
|
|
|
// assert!(!(matches!(expect_level, ExpectLevel::None) && is_list_accessor && !tail_present));
|
|
|
|
let mut no_tailing_discards = names_types_ids
|
|
.iter()
|
|
.rev()
|
|
.with_position()
|
|
.skip_while(|pos| match pos {
|
|
// Items are reversed order
|
|
Position::Last((name, _, _)) | Position::Middle((name, _, _)) => {
|
|
name == "_" && matches!(expect_level, ExpectLevel::None)
|
|
}
|
|
Position::First((name, _, _)) | Position::Only((name, _, _)) => {
|
|
name == "_" && (tail_present || matches!(expect_level, ExpectLevel::None))
|
|
}
|
|
})
|
|
.map(|position| match position {
|
|
Position::First(a) | Position::Middle(a) | Position::Last(a) | Position::Only(a) => a,
|
|
})
|
|
.collect_vec();
|
|
|
|
// If is just discards and check_last_item then we check for empty list
|
|
if no_tailing_discards.is_empty() {
|
|
if tail_present || matches!(expect_level, ExpectLevel::None) {
|
|
return term.lambda("_");
|
|
}
|
|
|
|
return Term::var("empty_list")
|
|
.choose_list(term.delay(), otherwise_delayed)
|
|
.force()
|
|
.lambda("empty_list");
|
|
}
|
|
|
|
// reverse back to original order
|
|
no_tailing_discards.reverse();
|
|
|
|
// If we cut off at least one element then that was tail and possibly some heads
|
|
let tail_wasnt_cutoff = tail_present && no_tailing_discards.len() == names_len;
|
|
|
|
let tail_name = |id| format!("tail_id_{}", id);
|
|
|
|
let head_item = |name, tipo: &Rc<Type>, tail_name: &str, then: Term<Name>| {
|
|
if name == "_" {
|
|
then
|
|
} else if tipo.is_pair() && is_list_accessor {
|
|
Term::head_list().apply(Term::var(tail_name.to_string()))
|
|
} else if matches!(expect_level, ExpectLevel::Full) {
|
|
// Expect level is full so we have an unknown piece of data to cast
|
|
if otherwise_delayed == Term::Error.delay() {
|
|
then.lambda(name).apply(unknown_data_to_type(
|
|
Term::head_list().apply(Term::var(tail_name.to_string())),
|
|
&tipo.to_owned(),
|
|
))
|
|
} else {
|
|
softcast_data_to_type_otherwise(
|
|
Term::head_list().apply(Term::var(tail_name.to_string())),
|
|
name,
|
|
&tipo.to_owned(),
|
|
then,
|
|
otherwise_delayed.clone(),
|
|
)
|
|
}
|
|
} else {
|
|
then.lambda(name).apply(known_data_to_type(
|
|
Term::head_list().apply(Term::var(tail_name.to_string())),
|
|
&tipo.to_owned(),
|
|
))
|
|
}
|
|
};
|
|
|
|
// Remember we reverse here so the First or Only is the last item
|
|
no_tailing_discards
|
|
.into_iter()
|
|
.rev()
|
|
.with_position()
|
|
.fold(term, |acc, position| {
|
|
match position {
|
|
Position::First((name, _, _)) | Position::Only((name, _, _))
|
|
if tail_wasnt_cutoff =>
|
|
{
|
|
// case for tail as the last item
|
|
acc.lambda(name)
|
|
}
|
|
|
|
Position::First((name, tipo, id)) | Position::Only((name, tipo, id)) => {
|
|
// case for no tail, but last item
|
|
let tail_name = tail_name(id);
|
|
|
|
// let head_item = head_item(name, tipo, &tail_name);
|
|
|
|
match expect_level {
|
|
ExpectLevel::None => {
|
|
head_item(name, tipo, &tail_name, acc).lambda(tail_name)
|
|
}
|
|
|
|
ExpectLevel::Full | ExpectLevel::Items => {
|
|
if otherwise_delayed == Term::Error.delay() && tail_present {
|
|
// No need to check last item if tail was present
|
|
head_item(name, tipo, &tail_name, acc).lambda(tail_name)
|
|
} else if tail_present {
|
|
// Custom error instead of trying to do head_item on a possibly empty list.
|
|
Term::var(tail_name.to_string())
|
|
.choose_list(
|
|
otherwise_delayed.clone(),
|
|
head_item(name, tipo, &tail_name, acc).delay(),
|
|
)
|
|
.force()
|
|
.lambda(tail_name)
|
|
} else if otherwise_delayed == Term::Error.delay() {
|
|
// Check head is last item in this list
|
|
head_item(
|
|
name,
|
|
tipo,
|
|
&tail_name,
|
|
Term::tail_list()
|
|
.apply(Term::var(tail_name.to_string()))
|
|
.choose_list(acc.delay(), Term::Error.delay())
|
|
.force(),
|
|
)
|
|
.lambda(tail_name)
|
|
} else {
|
|
// Custom error if list is not empty after this head
|
|
Term::var(tail_name.to_string())
|
|
.choose_list(
|
|
otherwise_delayed.clone(),
|
|
head_item(
|
|
name,
|
|
tipo,
|
|
&tail_name,
|
|
Term::tail_list()
|
|
.apply(Term::var(tail_name.to_string()))
|
|
.choose_list(acc.delay(), otherwise_delayed.clone())
|
|
.force(),
|
|
)
|
|
.delay(),
|
|
)
|
|
.force()
|
|
.lambda(tail_name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Position::Middle((name, tipo, id)) | Position::Last((name, tipo, id)) => {
|
|
// case for every item except the last item
|
|
let tail_name = tail_name(id);
|
|
|
|
// let head_item = head_item(name, tipo, &tail_name);
|
|
|
|
if matches!(expect_level, ExpectLevel::None)
|
|
|| otherwise_delayed == Term::Error.delay()
|
|
{
|
|
head_item(
|
|
name,
|
|
tipo,
|
|
&tail_name,
|
|
acc.apply(Term::tail_list().apply(Term::var(tail_name.to_string()))),
|
|
)
|
|
.lambda(tail_name)
|
|
} else {
|
|
// case for a custom error if the list is empty at this point
|
|
|
|
Term::var(tail_name.to_string())
|
|
.choose_list(
|
|
otherwise_delayed.clone(),
|
|
head_item(
|
|
name,
|
|
tipo,
|
|
&tail_name,
|
|
acc.apply(
|
|
Term::tail_list().apply(Term::var(tail_name.to_string())),
|
|
),
|
|
)
|
|
.delay(),
|
|
)
|
|
.force()
|
|
.lambda(tail_name)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
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 = known_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,
|
|
tipo: Rc<Type>,
|
|
count: usize,
|
|
mut args: Vec<Term<Name>>,
|
|
) -> Term<Name> {
|
|
match func {
|
|
DefaultFunction::ChooseUnit if count > 0 => {
|
|
let term = args.pop().unwrap();
|
|
let unit = args.pop().unwrap();
|
|
|
|
term.lambda("_").apply(unit)
|
|
}
|
|
|
|
DefaultFunction::MkCons => {
|
|
let arg_type = tipo
|
|
.arg_types()
|
|
.and_then(|generics| generics.first().cloned())
|
|
.expect("mk_cons should have (exactly) one type parameter");
|
|
|
|
if let [head, tail] = &args[..] {
|
|
Term::mk_cons()
|
|
.apply(if arg_type.is_pair() {
|
|
head.clone()
|
|
} else {
|
|
convert_type_to_data(head.clone(), &arg_type)
|
|
})
|
|
.apply(tail.clone())
|
|
} else {
|
|
unreachable!("mk_cons has two arguments.");
|
|
}
|
|
}
|
|
|
|
DefaultFunction::ChooseUnit
|
|
| 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::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: TraceLevel,
|
|
spend_name: String,
|
|
mint_name: String,
|
|
) -> Term<Name> {
|
|
match trace {
|
|
TraceLevel::Silent | TraceLevel::Compact => Term::equals_integer()
|
|
.apply(Term::integer(0.into()))
|
|
.apply(Term::var(CONSTR_INDEX_EXPOSER).apply(Term::var("__second_arg")))
|
|
.delayed_if_then_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"),
|
|
TraceLevel::Verbose => {
|
|
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.delayed_trace(Term::var("__incorrect_second_arg_type"));
|
|
|
|
let then_term = mint
|
|
.apply(Term::var("__first_arg"))
|
|
.apply(Term::var("__second_arg"));
|
|
|
|
let else_term = spend.apply(Term::var("__first_arg")).apply(
|
|
Term::head_list()
|
|
.apply(Term::var(CONSTR_FIELDS_EXPOSER).apply(Term::var("__second_arg"))),
|
|
);
|
|
|
|
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_then_else(
|
|
then_term.delayed_trace(Term::string(format!(
|
|
"Running 2 arg validator {}",
|
|
mint_name
|
|
))),
|
|
else_term.delayed_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")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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(), Some(UplcType::Data) | None) {
|
|
term = term
|
|
.lambda(arg.arg_name.get_variable_name().unwrap_or("_"))
|
|
.apply(known_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: TraceLevel) -> AirTree {
|
|
let otherwise = match trace {
|
|
TraceLevel::Silent | TraceLevel::Compact => AirTree::error(void(), true),
|
|
TraceLevel::Verbose => AirTree::trace(
|
|
AirTree::string("Validator returned false"),
|
|
void(),
|
|
AirTree::error(void(), true),
|
|
),
|
|
};
|
|
|
|
AirTree::if_branch(void(), air_tree, AirTree::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
|
|
}
|
|
|
|
pub fn get_src_code_by_span(
|
|
module_name: &str,
|
|
span: &Span,
|
|
module_src: &IndexMap<&str, &(String, LineNumbers)>,
|
|
) -> String {
|
|
let (src, _) = module_src
|
|
.get(module_name)
|
|
.unwrap_or_else(|| panic!("Missing module {module_name}"));
|
|
|
|
src.get(span.start..span.end)
|
|
.expect("Out of bounds span")
|
|
.to_string()
|
|
}
|
|
|
|
pub fn get_line_columns_by_span(
|
|
module_name: &str,
|
|
span: &Span,
|
|
module_src: &IndexMap<&str, &(String, LineNumbers)>,
|
|
) -> LineColumn {
|
|
let (_, lines) = module_src
|
|
.get(module_name)
|
|
.unwrap_or_else(|| panic!("Missing module {module_name}"));
|
|
|
|
lines
|
|
.line_and_column_number(span.start)
|
|
.expect("Out of bounds span")
|
|
}
|