diff --git a/benchmarks/lib/benchmarks/clausify/benchmark.ak b/benchmarks/lib/benchmarks/clausify/benchmark.ak index bf99a3575..c1261e653 100644 --- a/benchmarks/lib/benchmarks/clausify/benchmark.ak +++ b/benchmarks/lib/benchmarks/clausify/benchmark.ak @@ -232,8 +232,7 @@ fn split(formula: Formula) -> List { fn do_split(f: Formula, fs: List) -> List { when f is { Con(p, q) -> do_split(p, do_split(q, fs)) - _ -> - [f, ..fs] + _ -> [f, ..fs] } } @@ -261,14 +260,11 @@ fn tautclause(var: LRVars) -> Bool { /// insertion of an item into an ordered list fn insert_ordered(es: List, e: a, compare: fn(a, a) -> Ordering) -> List { when es is { - [] -> - [e] + [] -> [e] [head, ..tail] -> when compare(e, head) is { - Less -> - [e, ..es] - Greater -> - [head, ..insert_ordered(tail, e, compare)] + Less -> [e, ..es] + Greater -> [head, ..insert_ordered(tail, e, compare)] Equal -> es } } diff --git a/benchmarks/lib/benchmarks/knights/heuristic.ak b/benchmarks/lib/benchmarks/knights/heuristic.ak index f523156c1..5104be951 100644 --- a/benchmarks/lib/benchmarks/knights/heuristic.ak +++ b/benchmarks/lib/benchmarks/knights/heuristic.ak @@ -63,8 +63,7 @@ pub fn descendants(board: ChessSet) -> List { |> quicksort(compare_chess_set) |> list.map(fn(t) { t.2nd }) [_] -> singles - _ -> - [] + _ -> [] } } } diff --git a/benchmarks/lib/benchmarks/knights/sort.ak b/benchmarks/lib/benchmarks/knights/sort.ak index 2334de26f..37a127047 100644 --- a/benchmarks/lib/benchmarks/knights/sort.ak +++ b/benchmarks/lib/benchmarks/knights/sort.ak @@ -2,8 +2,7 @@ use aiken/collection/list pub fn quicksort(xs: List, compare: fn(a, a) -> Ordering) -> List { when xs is { - [] -> - [] + [] -> [] [head, ..tail] -> { let before = tail diff --git a/crates/aiken-lang/src/gen_uplc.rs b/crates/aiken-lang/src/gen_uplc.rs index e2f1abadc..2f7e96e4c 100644 --- a/crates/aiken-lang/src/gen_uplc.rs +++ b/crates/aiken-lang/src/gen_uplc.rs @@ -1,5 +1,6 @@ pub mod air; pub mod builder; +pub mod decision_tree; pub mod interner; pub mod tree; diff --git a/crates/aiken-lang/src/gen_uplc/decision_tree.rs b/crates/aiken-lang/src/gen_uplc/decision_tree.rs new file mode 100644 index 000000000..8d591a85b --- /dev/null +++ b/crates/aiken-lang/src/gen_uplc/decision_tree.rs @@ -0,0 +1,321 @@ +use std::{cmp::Ordering, rc::Rc}; + +use itertools::Itertools; + +use crate::{ + ast::{Pattern, TypedClause, TypedPattern}, + expr::{PatternConstructor, Type, TypedExpr}, +}; + +#[derive(Clone, Default, Copy)] +struct Occurrence { + passed_wild_card: bool, + amount: usize, +} + +#[derive(Clone)] +struct RowItem<'a> { + assign: Option, + pattern: &'a TypedPattern, +} + +#[derive(Clone, Eq, PartialEq)] +pub enum CaseTest { + Constr(PatternConstructor), + Int(String), + Bytes(Vec), + List(usize), + Wild, +} +impl PartialOrd for CaseTest { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (CaseTest::Wild, CaseTest::Wild) => Some(Ordering::Equal), + (CaseTest::Wild, _) => Some(Ordering::Less), + (_, CaseTest::Wild) => Some(Ordering::Greater), + (_, _) => Some(Ordering::Equal), + } + } +} + +impl Ord for CaseTest { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + match (self, other) { + (CaseTest::Wild, CaseTest::Wild) => Ordering::Equal, + (CaseTest::Wild, _) => Ordering::Less, + (_, CaseTest::Wild) => Ordering::Greater, + (_, _) => Ordering::Equal, + } + } +} + +struct Assign { + subject_name: String, + subject_tuple_index: Option, + assigned: String, +} + +enum DecisionTree { + Switch { + subject_name: String, + subject_tuple_index: Option, + subject_tipo: Rc, + column_to_test: usize, + cases: Vec<(CaseTest, DecisionTree)>, + default: Box, + }, + Leaf(TypedExpr), +} + +struct Row<'a> { + assigns: Vec, + columns: Vec>, + then: &'a TypedExpr, +} + +struct PatternMatrix<'a> { + rows: Vec>, +} + +fn map_to_row<'a>( + pattern: &'a TypedPattern, + subject_name: &String, + column_count: usize, +) -> Vec> { + match pattern { + Pattern::Var { name, .. } => vec![RowItem { + assign: Some(name.clone()), + pattern, + }] + .into_iter() + .cycle() + .take(column_count) + .collect_vec(), + + Pattern::Assign { name, pattern, .. } => { + let p = map_to_row(pattern, subject_name, column_count); + p.into_iter() + .map(|mut item| { + item.assign = Some(name.clone()); + item + }) + .collect_vec() + } + Pattern::Int { .. } + | Pattern::ByteArray { .. } + | Pattern::Discard { .. } + | Pattern::List { .. } + | Pattern::Constructor { .. } => vec![RowItem { + assign: None, + pattern, + }] + .into_iter() + .cycle() + .take(column_count) + .collect_vec(), + + Pattern::Pair { fst, snd, .. } => vec![ + RowItem { + assign: None, + pattern: fst, + }, + RowItem { + assign: None, + pattern: snd, + }, + ], + Pattern::Tuple { elems, .. } => elems + .iter() + .map(|elem| RowItem { + assign: None, + pattern: elem, + }) + .collect_vec(), + } +} + +fn match_wild_card(pattern: &TypedPattern) -> bool { + match pattern { + Pattern::Var { .. } | Pattern::Discard { .. } => true, + Pattern::Assign { pattern, .. } => match_wild_card(pattern), + _ => false, + } +} + +pub fn build_tree( + subject_name: &String, + subject_tipo: Rc, + clauses: &Vec, +) -> DecisionTree { + let column_count = if subject_tipo.is_pair() { + 2 + } else if subject_tipo.is_tuple() { + let Type::Tuple { elems, .. } = subject_tipo.as_ref() else { + unreachable!() + }; + elems.len() + } else { + 1 + }; + + let rows = clauses + .iter() + .map(|clause| { + let row_items = map_to_row(&clause.pattern, subject_name, column_count); + + Row { + assigns: vec![], + columns: row_items, + then: &clause.then, + } + }) + .collect_vec(); + + let subject_per_column = if column_count > 1 { + (0..column_count) + .map(|index| (subject_name.clone(), Some(index))) + .collect_vec() + } else { + vec![(subject_name.clone(), None)] + }; + + do_build_tree( + subject_name, + subject_tipo, + subject_per_column, + PatternMatrix { rows }, + ) +} + +pub fn do_build_tree<'a>( + subject_name: &String, + subject_tipo: Rc, + subject_per_column: Vec<(String, Option)>, + matrix: PatternMatrix<'a>, +) -> DecisionTree { + let column_count = if subject_tipo.is_pair() { + 2 + } else if subject_tipo.is_tuple() { + let Type::Tuple { elems, .. } = subject_tipo.as_ref() else { + unreachable!() + }; + elems.len() + } else { + 1 + }; + + let occurrences = [Occurrence::default()].repeat(column_count); + + let occurrences = + matrix + .rows + .iter() + .fold(occurrences, |mut occurrences: Vec, row| { + row.columns + .iter() + .enumerate() + .for_each(|(column_index, row_item)| { + let Some(occurrence_col) = occurrences.get_mut(column_index) else { + unreachable!() + }; + if !match_wild_card(row_item.pattern) && !occurrence_col.passed_wild_card { + occurrence_col.amount += 1; + } else { + occurrence_col.passed_wild_card = true; + } + }); + + occurrences + }); + + // index and count + let mut highest_occurrence = (0, 0); + + occurrences.iter().enumerate().for_each(|(index, occ)| { + if occ.amount > highest_occurrence.1 { + highest_occurrence.0 = index; + highest_occurrence.1 = occ.amount; + } + }); + + if column_count > 1 { + DecisionTree::Switch { + subject_name: subject_name.clone(), + subject_tuple_index: None, + subject_tipo: subject_tipo.clone(), + column_to_test: highest_occurrence.0, + cases: todo!(), + default: todo!(), + } + } else { + let mut collection_vec = matrix.rows.into_iter().fold( + vec![], + |mut collection_vec: Vec<(CaseTest, Vec>)>, mut item: Row<'a>| { + let col = item.columns.remove(highest_occurrence.0); + let mut patt = col.pattern; + + if let Pattern::Assign { pattern, .. } = patt { + patt = pattern; + } + + if let Some(assign) = col.assign { + item.assigns.push(Assign { + subject_name: subject_name.clone(), + subject_tuple_index: None, + assigned: assign, + }); + } + + let case = match patt { + Pattern::Int { value, .. } => CaseTest::Int(value.clone()), + Pattern::ByteArray { value, .. } => CaseTest::Bytes(value.clone()), + Pattern::Var { .. } | Pattern::Discard { .. } => CaseTest::Wild, + Pattern::List { elements, .. } => CaseTest::List(elements.len()), + Pattern::Constructor { constructor, .. } => { + CaseTest::Constr(constructor.clone()) + } + Pattern::Pair { .. } => todo!(), + Pattern::Tuple { .. } => todo!(), + _ => unreachable!(), + }; + + if let Some(index) = collection_vec.iter().position(|item| item.0 == case) { + let entry = collection_vec.get_mut(index).unwrap(); + + entry.1.push(item); + collection_vec + } else { + collection_vec.push((case, vec![item])); + + collection_vec + } + }, + ); + + collection_vec.sort_by(|a, b| a.0.cmp(&b.0)); + let mut collection_iter = collection_vec.into_iter().peekable(); + + let cases = collection_iter + .peeking_take_while(|a| !matches!(a.0, CaseTest::Wild)) + .collect_vec(); + + let mut fallback = collection_iter.collect_vec(); + + assert!(fallback.len() == 1); + + let fallback_matrix = PatternMatrix { + rows: fallback.remove(0).1, + }; + + DecisionTree::Switch { + subject_name: subject_name.clone(), + subject_tuple_index: None, + subject_tipo: subject_tipo.clone(), + column_to_test: highest_occurrence.0, + cases: todo!(), + default: todo!(), + } + }; + + todo!() +} diff --git a/examples/gift_card/validators/multi.ak b/examples/gift_card/validators/multi.ak index 35fbd8f3b..54563cff1 100644 --- a/examples/gift_card/validators/multi.ak +++ b/examples/gift_card/validators/multi.ak @@ -95,8 +95,7 @@ validator redeem(creator: ByteArray) { fn insert(self: List, e: a, compare: fn(a, a) -> Ordering) -> List { when self is { - [] -> - [e] + [] -> [e] [x, ..xs] -> if compare(e, x) == Less { [e, ..self] @@ -153,8 +152,7 @@ fn create_expected_minted_nfts( } else { let token_name = blake2b_256(bytearray.push(base, counter)) - let accum = - [token_name, ..accum] + let accum = [token_name, ..accum] create_expected_minted_nfts(base, counter - 1, accum) }