diff --git a/crates/aiken-lang/src/gen_uplc.rs b/crates/aiken-lang/src/gen_uplc.rs index c808d9f9..83d70b3f 100644 --- a/crates/aiken-lang/src/gen_uplc.rs +++ b/crates/aiken-lang/src/gen_uplc.rs @@ -825,6 +825,15 @@ impl<'a> CodeGenerator<'a> { clause_then_stack = clause_guard_stack; } + // deal with clause pattern and then itself + self.when_pattern( + &clause.pattern, + &mut clause_pattern_stack, + clause_then_stack, + subject_type, + clause_properties, + ); + match clause_properties { ClauseProperties::ConstrClause { original_subject_name, @@ -832,14 +841,6 @@ impl<'a> CodeGenerator<'a> { } => { let subject_name = original_subject_name.clone(); - self.when_pattern( - &clause.pattern, - &mut clause_pattern_stack, - clause_then_stack, - subject_type, - clause_properties, - ); - if clause.pattern.is_var() || clause.pattern.is_discard() { ir_stack.wrap_clause(clause_pattern_stack); } else { @@ -883,78 +884,66 @@ impl<'a> CodeGenerator<'a> { current_index, .. } => { - let (current_clause_index, has_tail) = - if let Pattern::List { elements, tail, .. } = &clause.pattern { - (elements.len(), tail.is_some()) - } else if let Pattern::Assign { pattern, .. } = &clause.pattern { - let Pattern::List { elements, tail, .. } = pattern.as_ref() else { - unreachable!("{:#?}", pattern) - }; - - (elements.len(), tail.is_some()) - } else { - unreachable!("{:#?}", &clause.pattern) - }; + let original_subject_name = original_subject_name.clone(); let prev_index = *current_index; - let subject_name = if current_clause_index == 0 { - original_subject_name.clone() - } else { - format!("__tail_{}", current_clause_index - 1) - }; + let elements_count_and_has_tail = + builder::get_list_elements_len_and_tail(&clause.pattern); - self.when_pattern( - &clause.pattern, - &mut clause_pattern_stack, - clause_then_stack, - subject_type, - clause_properties, - ); - - let next_tail = if index == clauses.len() - 1 { - None - } else { - let next_list_size = if let Pattern::List { elements, .. } = - &clauses[index + 1].pattern - { - elements.len() - } else if let Pattern::Assign { pattern, .. } = &clauses[index + 1].pattern - { - let Pattern::List { elements, .. } = pattern.as_ref() else { - unreachable!("{:#?}", pattern) - }; - elements.len() + if let Some((current_clause_index, has_tail)) = elements_count_and_has_tail { + let subject_name = if current_clause_index == 0 { + original_subject_name.clone() } else { - unreachable!() + format!("__tail_{}", current_clause_index - 1) }; - if next_list_size == current_clause_index { - None + // If current clause has already exposed all needed list items then no need to expose the + // same items again. + if current_clause_index as i64 - i64::from(has_tail) == prev_index { + ir_stack.wrap_clause(clause_pattern_stack); } else { - Some(format!("__tail_{current_clause_index}")) + let next_elements_count_and_has_tail = if index == clauses.len() - 1 { + None + } else { + builder::get_list_elements_len_and_tail( + &clauses + .get(index + 1) + .unwrap_or_else(|| { + unreachable!( + "We checked length how are we out of bounds" + ) + }) + .pattern, + ) + }; + + let next_tail = if let Some((next_elements_len, _)) = + next_elements_count_and_has_tail + { + if next_elements_len == current_clause_index { + None + } else { + Some(format!("__tail_{current_clause_index}")) + } + } else { + None + }; + + //mutate current index if we use list clause + *current_index = current_clause_index as i64; + + ir_stack.list_clause( + subject_type.clone(), + subject_name, + next_tail, + *clause_properties.is_complex_clause(), + clause_pattern_stack, + ); } - }; - - let minus_tail = has_tail as i64; - - if current_clause_index as i64 - minus_tail == prev_index { - ir_stack.wrap_clause(clause_pattern_stack); } else { - ir_stack.list_clause( - subject_type.clone(), - subject_name, - next_tail, - *clause_properties.is_complex_clause(), - clause_pattern_stack, - ); + ir_stack.wrap_clause(clause_pattern_stack); } - - let ClauseProperties::ListClause { current_index, .. } = clause_properties else { - unreachable!() - }; - - *current_index = current_clause_index as i64; } ClauseProperties::TupleClause { original_subject_name, @@ -964,14 +953,6 @@ impl<'a> CodeGenerator<'a> { let prev_defined_tuple_indices = defined_tuple_indices.clone(); let subject_name = original_subject_name.clone(); - self.when_pattern( - &clause.pattern, - &mut clause_pattern_stack, - clause_then_stack, - subject_type, - clause_properties, - ); - let current_defined_tuple_indices = match clause_properties { ClauseProperties::TupleClause { defined_tuple_indices, @@ -1215,7 +1196,7 @@ impl<'a> CodeGenerator<'a> { }) .collect_vec(); - if tail.is_some() && !elements.is_empty() { + if tail.is_some() && !tail_head_names.is_empty() { let tail_var = if elements.len() == 1 { clause_properties.original_subject_name().clone() } else { @@ -1234,7 +1215,7 @@ impl<'a> CodeGenerator<'a> { tail, nested_pattern, ); - } else if !elements.is_empty() { + } else if !tail_head_names.is_empty() { pattern_stack.list_expose( tipo.clone().into(), tail_head_names, @@ -1255,13 +1236,13 @@ impl<'a> CodeGenerator<'a> { } => { let data_type = builder::lookup_data_type_by_tipo(&self.data_types, tipo).unwrap(); - let (_, constructor_type) = data_type + let constructor_type = data_type .constructors .iter() - .enumerate() - .find(|(_, dt)| &dt.name == constr_name) + .find(|dt| &dt.name == constr_name) .unwrap(); let mut nested_pattern = pattern_stack.empty_with_scope(); + if *is_record { let field_map = match constructor { PatternConstructor::Record { field_map, .. } => field_map.clone().unwrap(), @@ -1278,13 +1259,16 @@ impl<'a> CodeGenerator<'a> { let arguments_index = arguments .iter() - .filter_map(|item| { + .enumerate() + .map(|(index, item)| { let label = item.label.clone().unwrap_or_default(); + let field_index = field_map .fields .get(&label) .map(|(index, _)| index) - .unwrap_or(&0); + .unwrap_or(&index); + let var_name = self.nested_pattern_ir_and_label( &item.value, &mut nested_pattern, @@ -1301,8 +1285,8 @@ impl<'a> CodeGenerator<'a> { ); var_name.map_or( - Some((label.clone(), "_".to_string(), *field_index)), - |var_name| Some((label, var_name, *field_index)), + (label.clone(), "_".to_string(), *field_index), + |var_name| (label, var_name, *field_index), ) }) .sorted_by(|item1, item2| item1.2.cmp(&item2.2)) @@ -1335,7 +1319,7 @@ impl<'a> CodeGenerator<'a> { let arguments_index = arguments .iter() .enumerate() - .filter_map(|(index, item)| { + .map(|(index, item)| { let var_name = self.nested_pattern_ir_and_label( &item.value, &mut nested_pattern, @@ -1343,9 +1327,7 @@ impl<'a> CodeGenerator<'a> { *clause_properties.is_final_clause(), ); - var_name.map_or(Some(("_".to_string(), index)), |var_name| { - Some((var_name, index)) - }) + var_name.map_or(("_".to_string(), index), |var_name| (var_name, index)) }) .collect::>(); @@ -1878,7 +1860,7 @@ impl<'a> CodeGenerator<'a> { let arguments_index = arguments .iter() .enumerate() - .filter_map(|(index, item)| { + .map(|(index, item)| { let label = item.label.clone().unwrap_or_default(); let field_index = if let Some(field_map) = &field_map { @@ -1896,11 +1878,10 @@ impl<'a> CodeGenerator<'a> { &assignment_properties, ); + // Note the stacks mutation here stacks.merge(nested_pattern); - name.map_or(Some(("_".to_string(), field_index)), |name| { - Some((name, field_index)) - }) + name.map_or(("_".to_string(), field_index), |name| (name, field_index)) }) .sorted_by(|item1, item2| item1.1.cmp(&item2.1)) .collect::>(); diff --git a/crates/aiken-lang/src/gen_uplc/builder.rs b/crates/aiken-lang/src/gen_uplc/builder.rs index 19033fdd..fc2bdffe 100644 --- a/crates/aiken-lang/src/gen_uplc/builder.rs +++ b/crates/aiken-lang/src/gen_uplc/builder.rs @@ -281,74 +281,93 @@ pub fn rearrange_clauses(clauses: Vec) -> Vec { let mut sorted_clauses = clauses; // if we have a list sort clauses so we can plug holes for cases not covered by clauses - // TODO: while having 10000000 element list is impossible to destructure in plutus budget, - // let's sort clauses by a safer manner - // TODO: how shall tails be weighted? Since any clause after will not run - sorted_clauses.sort_by(|clause1, clause2| { - let clause1_len = match &clause1.pattern { - Pattern::List { elements, tail, .. } => { - elements.len() * 3 - + usize::from(tail.is_some()) - + usize::from(clause1.guard.is_some()) - } - _ => 10000000, - }; - let clause2_len = match &clause2.pattern { - Pattern::List { elements, tail, .. } => elements.len() + usize::from(tail.is_some()), - _ => 10000001, - }; + // 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 clause1_len = match &clause1.pattern { + Pattern::List { elements, tail, .. } => { + Some(elements.len() + usize::from(tail.is_some())) + } + _ if clause1.guard.is_none() => Some(100000), + _ => None, + }; - clause1_len.cmp(&clause2_len) - }); + let clause2_len = match &clause2.pattern { + Pattern::List { elements, tail, .. } => { + Some(elements.len() + usize::from(tail.is_some())) + } + _ 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 elems_len = 0; let mut final_clauses = sorted_clauses.clone(); let mut holes_to_fill = vec![]; - let mut assign_plug_in_name = None; let mut last_clause_index = 0; let mut last_clause_set = false; // 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 = if sorted_clauses[sorted_clauses.len() - 1].guard.is_none() { - match &sorted_clauses[sorted_clauses.len() - 1].pattern { - Pattern::Var { name, .. } => { - assign_plug_in_name = Some(name); - sorted_clauses[sorted_clauses.len() - 1].clone().then - } - Pattern::Discard { .. } => sorted_clauses[sorted_clauses.len() - 1].clone().then, - _ => { - let tipo = sorted_clauses[sorted_clauses.len() - 1].then.tipo(); - TypedExpr::Trace { - location: Span::empty(), - tipo: tipo.clone(), - text: Box::new(TypedExpr::String { + 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: crate::builtins::string(), - value: "Clause not filled".to_string(), - }), - then: Box::new(TypedExpr::ErrorTerm { - location: Span::empty(), - tipo, - }), + 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 { - let tipo = sorted_clauses[sorted_clauses.len() - 1].then.tipo(); - TypedExpr::Trace { - location: Span::empty(), - tipo: tipo.clone(), - text: Box::new(TypedExpr::String { - location: Span::empty(), - tipo: crate::builtins::string(), - value: "Clause not filled".to_string(), - }), - then: Box::new(TypedExpr::ErrorTerm { - location: Span::empty(), - tipo, - }), - } + None }; for (index, clause) in sorted_clauses.iter().enumerate() { @@ -379,7 +398,7 @@ pub fn rearrange_clauses(clauses: Vec) -> Vec { .into(), }, guard: None, - then: plug_in_then.clone(), + then: plug_in_then(elems_len, last_clause), } } else { TypedClause { @@ -390,7 +409,7 @@ pub fn rearrange_clauses(clauses: Vec) -> Vec { tail: None, }, guard: None, - then: plug_in_then.clone(), + then: plug_in_then(elems_len, last_clause), } }; @@ -440,7 +459,7 @@ pub fn rearrange_clauses(clauses: Vec) -> Vec { location: Span::empty(), }, guard: None, - then: plug_in_then.clone(), + then: plug_in_then(index + 1, last_clause), }); } } @@ -461,6 +480,24 @@ pub fn rearrange_clauses(clauses: Vec) -> Vec { final_clauses } +/// 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>, +) -> 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 + } +} + #[allow(clippy::too_many_arguments)] pub fn list_access_to_uplc( names: &[String],