griffin_core/uplc/optimize/
shrinker.rs

1use super::interner::CodeGenInterner;
2use crate::pallas_primitives::conway::{BigInt, PlutusData};
3use crate::uplc::{
4    ast::{Constant, Data, Name, NamedDeBruijn, Program, Term, Type},
5    builder::{CONSTR_FIELDS_EXPOSER, CONSTR_INDEX_EXPOSER, INDICES_CONVERTER},
6    builtins::DefaultFunction,
7    machine::{cost_model::ExBudget, runtime::Compressable, value::from_pallas_bigint},
8};
9use alloc::{
10    rc::Rc,
11    string::{String, ToString},
12    vec::Vec,
13};
14use blst::{blst_p1, blst_p2};
15use core::{cmp::Ordering, iter, ops::Neg};
16use hashbrown::HashMap;
17use itertools::{FoldWhile, Itertools};
18use strum::IntoEnumIterator;
19
20#[derive(Eq, Hash, PartialEq, Clone, Debug, PartialOrd)]
21pub enum ScopePath {
22    FUNC,
23    ARG,
24}
25
26#[derive(Eq, Hash, PartialEq, Clone, Debug, Default, PartialOrd)]
27pub struct Scope {
28    scope: Vec<ScopePath>,
29}
30
31impl Scope {
32    pub fn new() -> Self {
33        Self { scope: vec![] }
34    }
35
36    pub fn push(&self, path: ScopePath) -> Self {
37        let mut new_scope = self.scope.clone();
38        new_scope.push(path);
39        Self { scope: new_scope }
40    }
41
42    pub fn pop(&self) -> Self {
43        let mut new_scope = self.scope.clone();
44        new_scope.pop();
45        Self { scope: new_scope }
46    }
47
48    pub fn common_ancestor(&self, other: &Scope) -> Self {
49        Self {
50            scope: self
51                .scope
52                .iter()
53                .zip(other.scope.iter())
54                .map_while(|(a, b)| if a == b { Some(a) } else { None })
55                .cloned()
56                .collect_vec(),
57        }
58    }
59
60    pub fn is_common_ancestor(&self, other: &Scope) -> bool {
61        self == &self.common_ancestor(other)
62    }
63
64    pub fn len(&self) -> usize {
65        self.scope.len()
66    }
67
68    pub fn is_empty(&self) -> bool {
69        self.len() == 0
70    }
71}
72
73pub struct IdGen {
74    id: usize,
75}
76
77impl IdGen {
78    pub fn new() -> Self {
79        Self { id: 0 }
80    }
81
82    pub fn next_id(&mut self) -> usize {
83        self.id += 1;
84        self.id
85    }
86}
87
88impl Default for IdGen {
89    fn default() -> Self {
90        Self::new()
91    }
92}
93
94pub const NO_INLINE: &str = "__no_inline__";
95
96#[derive(PartialEq, PartialOrd, Default, Debug, Clone)]
97pub struct VarLookup {
98    found: bool,
99    occurrences: usize,
100    delays: usize,
101    no_inline: bool,
102}
103
104impl VarLookup {
105    pub fn new() -> Self {
106        Self {
107            found: false,
108            occurrences: 0,
109            delays: 0,
110            no_inline: false,
111        }
112    }
113
114    pub fn new_found() -> Self {
115        Self {
116            found: true,
117            occurrences: 1,
118            delays: 0,
119            no_inline: false,
120        }
121    }
122
123    pub fn combine(self, other: Self) -> Self {
124        Self {
125            found: self.found || other.found,
126            occurrences: self.occurrences + other.occurrences,
127            delays: self.delays + other.delays,
128            no_inline: self.no_inline || other.no_inline,
129        }
130    }
131
132    pub fn delay_if_found(self, delay_amount: usize) -> Self {
133        if self.found {
134            Self {
135                found: self.found,
136                occurrences: self.occurrences,
137                delays: self.delays + delay_amount,
138                no_inline: self.no_inline,
139            }
140        } else {
141            self
142        }
143    }
144
145    pub fn no_inline_if_found(self) -> Self {
146        if self.found {
147            Self {
148                found: self.found,
149                occurrences: self.occurrences,
150                delays: self.delays,
151                no_inline: true,
152            }
153        } else {
154            self
155        }
156    }
157}
158
159impl DefaultFunction {
160    pub fn is_order_agnostic_builtin(self) -> bool {
161        matches!(
162            self,
163            DefaultFunction::AddInteger
164                | DefaultFunction::MultiplyInteger
165                | DefaultFunction::EqualsInteger
166                | DefaultFunction::EqualsByteString
167                | DefaultFunction::EqualsString
168                | DefaultFunction::EqualsData
169                | DefaultFunction::Bls12_381_G1_Equal
170                | DefaultFunction::Bls12_381_G2_Equal
171                | DefaultFunction::Bls12_381_G1_Add
172                | DefaultFunction::Bls12_381_G2_Add
173        )
174    }
175    /// For now all of the curry builtins are not forceable
176    /// Curryable builtins must take in 2 or more arguments
177    pub fn can_curry_builtin(self) -> bool {
178        matches!(
179            self,
180            DefaultFunction::AddInteger
181                | DefaultFunction::SubtractInteger
182                | DefaultFunction::MultiplyInteger
183                | DefaultFunction::DivideInteger
184                | DefaultFunction::ModInteger
185                | DefaultFunction::QuotientInteger
186                | DefaultFunction::RemainderInteger
187                | DefaultFunction::EqualsInteger
188                | DefaultFunction::EqualsByteString
189                | DefaultFunction::EqualsString
190                | DefaultFunction::EqualsData
191                | DefaultFunction::Bls12_381_G1_Equal
192                | DefaultFunction::Bls12_381_G2_Equal
193                | DefaultFunction::LessThanInteger
194                | DefaultFunction::LessThanEqualsInteger
195                | DefaultFunction::AppendByteString
196                | DefaultFunction::ConsByteString
197                | DefaultFunction::SliceByteString
198                | DefaultFunction::IndexByteString
199                | DefaultFunction::LessThanEqualsByteString
200                | DefaultFunction::LessThanByteString
201                | DefaultFunction::AppendString
202                | DefaultFunction::Bls12_381_G1_Add
203                | DefaultFunction::Bls12_381_G2_Add
204                | DefaultFunction::ConstrData
205        )
206    }
207
208    pub fn is_error_safe(self, arg_stack: &[&Term<Name>]) -> bool {
209        match self {
210            DefaultFunction::AddInteger
211            | DefaultFunction::SubtractInteger
212            | DefaultFunction::MultiplyInteger
213            | DefaultFunction::EqualsInteger
214            | DefaultFunction::LessThanInteger
215            | DefaultFunction::LessThanEqualsInteger
216            | DefaultFunction::IData => arg_stack.iter().all(|arg| {
217                if let Term::Constant(c) = arg {
218                    matches!(c.as_ref(), Constant::Integer(_))
219                } else {
220                    false
221                }
222            }),
223            DefaultFunction::DivideInteger
224            | DefaultFunction::ModInteger
225            | DefaultFunction::QuotientInteger
226            | DefaultFunction::RemainderInteger => arg_stack.iter().all(|arg| {
227                if let Term::Constant(c) = arg {
228                    if let Constant::Integer(i) = c.as_ref() {
229                        *i != 0.into()
230                    } else {
231                        false
232                    }
233                } else {
234                    false
235                }
236            }),
237            DefaultFunction::LengthOfByteString
238            | DefaultFunction::EqualsByteString
239            | DefaultFunction::AppendByteString
240            | DefaultFunction::LessThanEqualsByteString
241            | DefaultFunction::LessThanByteString
242            | DefaultFunction::BData => arg_stack.iter().all(|arg| {
243                if let Term::Constant(c) = arg {
244                    matches!(c.as_ref(), Constant::ByteString(_))
245                } else {
246                    false
247                }
248            }),
249
250            DefaultFunction::ConsByteString => {
251                if let (Term::Constant(c), Term::Constant(c2)) = (&arg_stack[0], &arg_stack[1]) {
252                    if let (Constant::Integer(i), Constant::ByteString(_)) =
253                        (c.as_ref(), c2.as_ref())
254                    {
255                        i >= &0.into() && i < &256.into()
256                    } else {
257                        false
258                    }
259                } else {
260                    false
261                }
262            }
263
264            DefaultFunction::SliceByteString => {
265                if let (Term::Constant(c), Term::Constant(c2), Term::Constant(c3)) =
266                    (&arg_stack[0], &arg_stack[1], &arg_stack[2])
267                {
268                    matches!(
269                        (c.as_ref(), c2.as_ref(), c3.as_ref()),
270                        (
271                            Constant::Integer(_),
272                            Constant::Integer(_),
273                            Constant::ByteString(_)
274                        )
275                    )
276                } else {
277                    false
278                }
279            }
280
281            DefaultFunction::IndexByteString => {
282                if let (Term::Constant(c), Term::Constant(c2)) = (&arg_stack[0], &arg_stack[1]) {
283                    if let (Constant::ByteString(bs), Constant::Integer(i)) =
284                        (c.as_ref(), c2.as_ref())
285                    {
286                        i >= &0.into() && i < &bs.len().into()
287                    } else {
288                        false
289                    }
290                } else {
291                    false
292                }
293            }
294
295            DefaultFunction::EqualsString
296            | DefaultFunction::AppendString
297            | DefaultFunction::EncodeUtf8 => arg_stack.iter().all(|arg| {
298                if let Term::Constant(c) = arg {
299                    matches!(c.as_ref(), Constant::String(_))
300                } else {
301                    false
302                }
303            }),
304
305            DefaultFunction::EqualsData | DefaultFunction::SerialiseData => {
306                arg_stack.iter().all(|arg| {
307                    if let Term::Constant(c) = arg {
308                        matches!(c.as_ref(), Constant::Data(_))
309                    } else {
310                        false
311                    }
312                })
313            }
314
315            DefaultFunction::Bls12_381_G1_Equal | DefaultFunction::Bls12_381_G1_Add => {
316                arg_stack.iter().all(|arg| {
317                    if let Term::Constant(c) = arg {
318                        matches!(c.as_ref(), Constant::Bls12_381G1Element(_))
319                    } else {
320                        false
321                    }
322                })
323            }
324
325            DefaultFunction::Bls12_381_G2_Equal | DefaultFunction::Bls12_381_G2_Add => {
326                arg_stack.iter().all(|arg| {
327                    if let Term::Constant(c) = arg {
328                        matches!(c.as_ref(), Constant::Bls12_381G2Element(_))
329                    } else {
330                        false
331                    }
332                })
333            }
334
335            DefaultFunction::ConstrData => {
336                if let (Term::Constant(c), Term::Constant(c2)) = (&arg_stack[0], &arg_stack[1]) {
337                    if let (Constant::Integer(i), Constant::ProtoList(Type::Data, _)) =
338                        (c.as_ref(), c2.as_ref())
339                    {
340                        i >= &0.into()
341                    } else {
342                        false
343                    }
344                } else {
345                    false
346                }
347            }
348
349            _ => false,
350        }
351    }
352
353    pub fn wrapped_name(self) -> String {
354        format!("__{}_wrapped", self.aiken_name())
355    }
356}
357pub fn forceable_wrapped_names() -> Vec<String> {
358    DefaultFunction::iter()
359        .filter(|df| df.force_count() > 0)
360        .map(|df| df.wrapped_name())
361        .collect_vec()
362}
363
364#[derive(PartialEq, Clone, Debug)]
365pub enum BuiltinArgs {
366    TwoArgs {
367        fst: (usize, Term<Name>),
368        snd: Option<(usize, Term<Name>)>,
369    },
370    ThreeArgs {
371        fst: (usize, Term<Name>),
372        snd: Option<(usize, Term<Name>)>,
373        thd: Option<(usize, Term<Name>)>,
374    },
375    TwoArgsAnyOrder {
376        fst: (usize, Term<Name>),
377        snd: Option<(usize, Term<Name>)>,
378    },
379}
380
381impl BuiltinArgs {
382    fn args_from_arg_stack(stack: Vec<(usize, Term<Name>)>, func: DefaultFunction) -> Self {
383        let error_safe = false;
384
385        let mut ordered_arg_stack = stack.into_iter().sorted_by(|(_, arg1), (_, arg2)| {
386            // sort by constant first if the builtin is order agnostic
387            if func.is_order_agnostic_builtin() {
388                if matches!(arg1, Term::Constant(_)) == matches!(arg2, Term::Constant(_)) {
389                    Ordering::Equal
390                } else if matches!(arg1, Term::Constant(_)) {
391                    Ordering::Less
392                } else {
393                    Ordering::Greater
394                }
395            } else {
396                Ordering::Equal
397            }
398        });
399
400        if ordered_arg_stack.len() == 2 && func.is_order_agnostic_builtin() {
401            // This is the special case where the order of args is irrelevant to the builtin
402            // An example is addInteger or multiplyInteger
403            BuiltinArgs::TwoArgsAnyOrder {
404                fst: ordered_arg_stack.next().unwrap(),
405                snd: if error_safe {
406                    ordered_arg_stack.next()
407                } else {
408                    None
409                },
410            }
411        } else if ordered_arg_stack.len() == 2 {
412            BuiltinArgs::TwoArgs {
413                fst: ordered_arg_stack.next().unwrap(),
414                snd: if error_safe {
415                    ordered_arg_stack.next()
416                } else {
417                    None
418                },
419            }
420        } else {
421            BuiltinArgs::ThreeArgs {
422                fst: ordered_arg_stack.next().unwrap(),
423                snd: ordered_arg_stack.next(),
424                thd: if error_safe {
425                    ordered_arg_stack.next()
426                } else {
427                    None
428                },
429            }
430        }
431    }
432
433    fn args_to_curried_args(self, builtin: DefaultFunction) -> CurriedBuiltin {
434        let args = match self {
435            BuiltinArgs::TwoArgs { fst, snd } | BuiltinArgs::TwoArgsAnyOrder { fst, snd } => {
436                CurriedArgs::TwoArgs {
437                    fst_args: vec![CurriedNode {
438                        id: fst.0,
439                        term: fst.1,
440                    }],
441                    snd_args: snd
442                        .into_iter()
443                        .map(|item| CurriedNode {
444                            id: item.0,
445                            term: item.1,
446                        })
447                        .collect_vec(),
448                }
449            }
450            BuiltinArgs::ThreeArgs { fst, snd, thd } => CurriedArgs::ThreeArgs {
451                fst_args: vec![CurriedNode {
452                    id: fst.0,
453                    term: fst.1,
454                }],
455                snd_args: snd
456                    .into_iter()
457                    .map(|item| CurriedNode {
458                        id: item.0,
459                        term: item.1,
460                    })
461                    .collect_vec(),
462                thd_args: thd
463                    .into_iter()
464                    .map(|item| CurriedNode {
465                        id: item.0,
466                        term: item.1,
467                    })
468                    .collect_vec(),
469            },
470        };
471
472        CurriedBuiltin {
473            func: builtin,
474            args,
475        }
476    }
477
478    pub fn get_id_args(self) -> Vec<UplcNode> {
479        match self {
480            BuiltinArgs::TwoArgs { fst, snd } | BuiltinArgs::TwoArgsAnyOrder { fst, snd } => {
481                iter::once(fst)
482                    .chain(snd)
483                    .map(|item| UplcNode {
484                        applied_id: item.0,
485                        curried_id: item.0,
486                        term: item.1,
487                    })
488                    .collect_vec()
489            }
490            BuiltinArgs::ThreeArgs { fst, snd, thd } => iter::once(fst)
491                .chain(snd)
492                .chain(thd)
493                .map(|item| UplcNode {
494                    applied_id: item.0,
495                    curried_id: item.0,
496                    term: item.1,
497                })
498                .collect_vec(),
499        }
500    }
501}
502
503#[derive(PartialEq, Clone, Debug)]
504pub struct CurriedNode {
505    id: usize,
506    term: Term<Name>,
507}
508
509#[derive(PartialEq, Clone, Debug)]
510pub struct UplcNode {
511    applied_id: usize,
512    curried_id: usize,
513    term: Term<Name>,
514}
515
516#[derive(Eq, Hash, PartialEq, Clone, Debug)]
517pub struct CurriedName {
518    func_name: String,
519    id_vec: Vec<usize>,
520}
521
522impl CurriedName {
523    pub fn len(&self) -> usize {
524        self.id_vec.len()
525    }
526
527    pub fn is_empty(&self) -> bool {
528        self.len() == 0
529    }
530}
531
532#[derive(PartialEq, Clone, Debug)]
533pub enum CurriedArgs {
534    TwoArgs {
535        fst_args: Vec<CurriedNode>,
536        snd_args: Vec<CurriedNode>,
537    },
538    ThreeArgs {
539        fst_args: Vec<CurriedNode>,
540        snd_args: Vec<CurriedNode>,
541        thd_args: Vec<CurriedNode>,
542    },
543}
544
545impl CurriedArgs {
546    pub fn merge_node_by_path(self, path: BuiltinArgs) -> Self {
547        match (self, path) {
548            (
549                CurriedArgs::TwoArgs {
550                    mut fst_args,
551                    mut snd_args,
552                },
553                BuiltinArgs::TwoArgs { fst, snd },
554            ) => {
555                let fst_args = match fst_args.iter_mut().find(|item| item.term == fst.1) {
556                    None => {
557                        fst_args.push(CurriedNode {
558                            id: fst.0,
559                            term: fst.1,
560                        });
561                        fst_args
562                    }
563                    _ => fst_args,
564                };
565
566                let snd_args = match snd_args.iter_mut().find(|item| match &snd {
567                    Some(snd) => item.term == snd.1,
568                    None => false,
569                }) {
570                    None => snd_args
571                        .into_iter()
572                        .chain(snd.into_iter().map(|item| CurriedNode {
573                            id: item.0,
574                            term: item.1,
575                        }))
576                        .collect_vec(),
577                    _ => snd_args,
578                };
579
580                CurriedArgs::TwoArgs { fst_args, snd_args }
581            }
582            (
583                CurriedArgs::TwoArgs {
584                    mut fst_args,
585                    mut snd_args,
586                },
587                BuiltinArgs::TwoArgsAnyOrder { mut fst, snd },
588            ) => {
589                let (switched, fst_args) = if fst_args.iter_mut().any(|item| item.term == fst.1) {
590                    (false, fst_args)
591                } else if fst_args.iter_mut().any(|item| match &snd {
592                    Some(snd) => item.term == snd.1,
593                    None => false,
594                }) {
595                    (true, fst_args)
596                } else {
597                    fst_args.push(CurriedNode {
598                        id: fst.0,
599                        // Replace the value here instead of cloning since
600                        // switched must be false here
601                        // I use Term::Error.force() since it's not a
602                        // naturally occurring term in code gen.
603                        term: core::mem::replace(&mut fst.1, Term::Error.force()),
604                    });
605
606                    (false, fst_args)
607                };
608
609                // If switched then put the first arg in the second arg slot
610                let snd_args = if switched {
611                    assert!(fst.1 != Term::Error.force());
612
613                    if snd_args.iter_mut().any(|item| item.term == fst.1) {
614                        snd_args
615                    } else {
616                        snd_args.push(CurriedNode {
617                            id: fst.0,
618                            term: fst.1,
619                        });
620                        snd_args
621                    }
622                } else if snd_args.iter_mut().any(|item| match &snd {
623                    Some(snd) => item.term == snd.1,
624                    None => false,
625                }) {
626                    snd_args
627                } else {
628                    snd_args
629                        .into_iter()
630                        .chain(snd.into_iter().map(|item| CurriedNode {
631                            id: item.0,
632                            term: item.1,
633                        }))
634                        .collect_vec()
635                };
636
637                CurriedArgs::TwoArgs { fst_args, snd_args }
638            }
639            (
640                CurriedArgs::ThreeArgs {
641                    mut fst_args,
642                    mut snd_args,
643                    mut thd_args,
644                },
645                BuiltinArgs::ThreeArgs { fst, snd, thd },
646            ) => {
647                let fst_args = match fst_args.iter_mut().find(|item| item.term == fst.1) {
648                    None => {
649                        fst_args.push(CurriedNode {
650                            id: fst.0,
651                            term: fst.1,
652                        });
653                        fst_args
654                    }
655                    _ => fst_args,
656                };
657
658                let snd_args = match snd_args.iter_mut().find(|item| match &snd {
659                    Some(snd) => item.term == snd.1,
660                    None => false,
661                }) {
662                    None => snd_args
663                        .into_iter()
664                        .chain(snd.into_iter().map(|item| CurriedNode {
665                            id: item.0,
666                            term: item.1,
667                        }))
668                        .collect_vec(),
669
670                    _ => snd_args,
671                };
672
673                let thd_args = match thd_args.iter_mut().find(|item| match &thd {
674                    Some(thd) => item.term == thd.1,
675                    None => false,
676                }) {
677                    None => thd_args
678                        .into_iter()
679                        .chain(thd.into_iter().map(|item| CurriedNode {
680                            id: item.0,
681                            term: item.1,
682                        }))
683                        .collect_vec(),
684
685                    _ => thd_args,
686                };
687
688                CurriedArgs::ThreeArgs {
689                    fst_args,
690                    snd_args,
691                    thd_args,
692                }
693            }
694            _ => unreachable!(),
695        }
696    }
697
698    // TODO: switch clones to memory moves out of path
699    fn get_id_args(&self, path: BuiltinArgs) -> Option<Vec<UplcNode>> {
700        match (self, path) {
701            (CurriedArgs::TwoArgs { fst_args, snd_args }, BuiltinArgs::TwoArgs { fst, snd }) => {
702                let arg = fst_args.iter().find(|item| fst.1 == item.term)?;
703
704                let Some(arg2) = snd_args.iter().find(|item| match &snd {
705                    Some(snd) => item.term == snd.1,
706                    None => false,
707                }) else {
708                    return Some(vec![UplcNode {
709                        applied_id: fst.0,
710                        curried_id: arg.id,
711                        term: arg.term.clone(),
712                    }]);
713                };
714
715                Some(vec![
716                    UplcNode {
717                        applied_id: fst.0,
718                        curried_id: arg.id,
719                        term: arg.term.clone(),
720                    },
721                    UplcNode {
722                        applied_id: snd.as_ref().unwrap().0,
723                        curried_id: arg2.id,
724                        term: arg2.term.clone(),
725                    },
726                ])
727            }
728            (
729                CurriedArgs::TwoArgs { fst_args, snd_args },
730                BuiltinArgs::TwoArgsAnyOrder { fst, snd },
731            ) => {
732                let mut id_vec = vec![];
733
734                if let Some(arg) = fst_args.iter().find(|item| item.term == fst.1) {
735                    id_vec.push(UplcNode {
736                        applied_id: fst.0,
737                        curried_id: arg.id,
738                        term: arg.term.clone(),
739                    });
740
741                    let Some(arg2) = snd_args.iter().find(|item| match &snd {
742                        Some(snd) => snd.1 == item.term,
743                        None => false,
744                    }) else {
745                        return Some(id_vec);
746                    };
747
748                    id_vec.push(UplcNode {
749                        applied_id: snd.as_ref().unwrap().0,
750                        curried_id: arg2.id,
751                        term: arg2.term.clone(),
752                    });
753
754                    Some(id_vec)
755                } else if let Some(arg) = fst_args.iter().find(|item| match &snd {
756                    Some(snd) => item.term == snd.1,
757                    None => false,
758                }) {
759                    id_vec.push(UplcNode {
760                        applied_id: snd.as_ref().unwrap().0,
761                        curried_id: arg.id,
762                        term: arg.term.clone(),
763                    });
764
765                    let Some(arg2) = snd_args.iter().find(|item| item.term == fst.1) else {
766                        return Some(id_vec);
767                    };
768
769                    id_vec.push(UplcNode {
770                        applied_id: fst.0,
771                        curried_id: arg2.id,
772                        term: arg2.term.clone(),
773                    });
774
775                    Some(id_vec)
776                } else {
777                    None
778                }
779            }
780
781            (
782                CurriedArgs::ThreeArgs {
783                    fst_args,
784                    snd_args,
785                    thd_args,
786                },
787                BuiltinArgs::ThreeArgs { fst, snd, thd },
788            ) => {
789                let arg = fst_args.iter().find(|item| fst.1 == item.term)?;
790
791                let Some(arg2) = snd_args.iter().find(|item| match &snd {
792                    Some(snd) => item.term == snd.1,
793                    None => false,
794                }) else {
795                    return Some(vec![UplcNode {
796                        applied_id: fst.0,
797                        curried_id: arg.id,
798                        term: arg.term.clone(),
799                    }]);
800                };
801
802                let Some(arg3) = thd_args.iter().find(|item| match &thd {
803                    Some(thd) => item.term == thd.1,
804                    None => false,
805                }) else {
806                    return Some(vec![
807                        UplcNode {
808                            applied_id: fst.0,
809                            curried_id: arg.id,
810                            term: arg.term.clone(),
811                        },
812                        UplcNode {
813                            applied_id: snd.as_ref().unwrap().0,
814                            curried_id: arg2.id,
815                            term: arg2.term.clone(),
816                        },
817                    ]);
818                };
819
820                Some(vec![
821                    UplcNode {
822                        applied_id: fst.0,
823                        curried_id: arg.id,
824                        term: arg.term.clone(),
825                    },
826                    UplcNode {
827                        applied_id: snd.as_ref().unwrap().0,
828                        curried_id: arg2.id,
829                        term: arg2.term.clone(),
830                    },
831                    UplcNode {
832                        applied_id: thd.as_ref().unwrap().0,
833                        curried_id: arg3.id,
834                        term: arg3.term.clone(),
835                    },
836                ])
837            }
838            _ => unreachable!(),
839        }
840    }
841
842    fn is_flipped(&self, path: &BuiltinArgs) -> bool {
843        match (self, path) {
844            (CurriedArgs::TwoArgs { fst_args, .. }, BuiltinArgs::TwoArgsAnyOrder { fst, snd }) => {
845                if fst_args.iter().any(|item| item.term == fst.1) {
846                    false
847                } else {
848                    fst_args.iter().any(|item| match &snd {
849                        Some(snd) => item.term == snd.1,
850                        None => false,
851                    })
852                }
853            }
854            _ => false,
855        }
856    }
857}
858#[derive(PartialEq, Clone, Debug)]
859pub struct CurriedBuiltin {
860    pub func: DefaultFunction,
861    /// For use with subtract integer where we can flip the order of the arguments
862    /// if the second argument is a constant
863    pub args: CurriedArgs,
864}
865
866impl CurriedBuiltin {
867    pub fn merge_node_by_path(self, path: BuiltinArgs) -> Self {
868        Self {
869            func: self.func,
870            args: self.args.merge_node_by_path(path),
871        }
872    }
873
874    pub fn get_id_args(&self, path: BuiltinArgs) -> Option<Vec<UplcNode>> {
875        self.args.get_id_args(path)
876    }
877
878    pub fn is_flipped(&self, path: &BuiltinArgs) -> bool {
879        self.args.is_flipped(path)
880    }
881}
882
883#[derive(Debug, Clone)]
884pub struct Context {
885    pub inlined_apply_ids: Vec<usize>,
886    pub constants_to_flip: Vec<usize>,
887    pub write_bits_indices_arg: Vec<usize>,
888    pub builtins_map: HashMap<DefaultFunction, ()>,
889    pub blst_p1_list: Vec<blst_p1>,
890    pub blst_p2_list: Vec<blst_p2>,
891    pub write_bits_convert: bool,
892    pub node_count: usize,
893}
894
895#[derive(Clone, Debug)]
896pub enum Args {
897    Force(usize),
898    Apply(usize, Term<Name>),
899}
900
901impl Term<Name> {
902    fn traverse_uplc_with_helper(
903        &mut self,
904        scope: &Scope,
905        mut arg_stack: Vec<Args>,
906        id_gen: &mut IdGen,
907        with: &mut impl FnMut(Option<usize>, &mut Term<Name>, Vec<Args>, &Scope, &mut Context),
908        context: &mut Context,
909        inline_lambda: bool,
910    ) {
911        match self {
912            Term::Apply { function, argument } => {
913                let arg = Rc::make_mut(argument);
914
915                arg.traverse_uplc_with_helper(
916                    &scope.push(ScopePath::ARG),
917                    vec![],
918                    id_gen,
919                    with,
920                    context,
921                    inline_lambda,
922                );
923                let apply_id = id_gen.next_id();
924
925                // Here we must clone since we must leave the original AST alone
926                arg_stack.push(Args::Apply(apply_id, arg.clone()));
927
928                let func = Rc::make_mut(function);
929
930                func.traverse_uplc_with_helper(
931                    &scope.push(ScopePath::FUNC),
932                    arg_stack,
933                    id_gen,
934                    with,
935                    context,
936                    inline_lambda,
937                );
938
939                with(Some(apply_id), self, vec![], scope, context);
940            }
941            Term::Force(f) => {
942                let f = Rc::make_mut(f);
943                let force_id = id_gen.next_id();
944
945                arg_stack.push(Args::Force(force_id));
946
947                f.traverse_uplc_with_helper(scope, arg_stack, id_gen, with, context, inline_lambda);
948
949                with(Some(force_id), self, vec![], scope, context);
950            }
951            Term::Delay(d) => {
952                let d = Rc::make_mut(d);
953                let delay_arg = arg_stack
954                    .pop()
955                    .map(|arg| {
956                        assert!(matches!(arg, Args::Force(_)));
957                        vec![arg]
958                    })
959                    .unwrap_or_default();
960
961                d.traverse_uplc_with_helper(scope, arg_stack, id_gen, with, context, inline_lambda);
962
963                with(None, self, delay_arg, scope, context);
964            }
965            Term::Lambda {
966                parameter_name,
967                body,
968            } => {
969                let p = parameter_name.clone();
970
971                // Lambda pops one item off the arg stack. If there is no item then it is a unsaturated lambda
972                // NO_INLINE lambdas come in with 0 arguments on the arg stack
973                let args = if parameter_name.text == NO_INLINE {
974                    vec![]
975                } else {
976                    arg_stack
977                        .pop()
978                        .map(|arg| {
979                            assert!(matches!(arg, Args::Apply(_, _)));
980                            vec![arg]
981                        })
982                        .unwrap_or_default()
983                };
984
985                if inline_lambda {
986                    // Pass in either one or zero args.
987                    // For lambda we run the function with first then recurse on the body or replaced term
988
989                    with(None, self, args, scope, context);
990
991                    match self {
992                        Term::Lambda {
993                            parameter_name,
994                            body,
995                        } if *parameter_name == p => {
996                            let body = Rc::make_mut(body);
997                            body.traverse_uplc_with_helper(
998                                scope,
999                                arg_stack,
1000                                id_gen,
1001                                with,
1002                                context,
1003                                inline_lambda,
1004                            );
1005                        }
1006                        other => other.traverse_uplc_with_helper(
1007                            scope,
1008                            arg_stack,
1009                            id_gen,
1010                            with,
1011                            context,
1012                            inline_lambda,
1013                        ),
1014                    }
1015                } else {
1016                    let body = Rc::make_mut(body);
1017
1018                    body.traverse_uplc_with_helper(
1019                        scope,
1020                        arg_stack,
1021                        id_gen,
1022                        with,
1023                        context,
1024                        inline_lambda,
1025                    );
1026
1027                    with(None, self, args, scope, context);
1028                }
1029            }
1030
1031            Term::Case { constr, branches } => {
1032                let constr = Rc::make_mut(constr);
1033                constr.traverse_uplc_with_helper(
1034                    scope,
1035                    vec![],
1036                    id_gen,
1037                    with,
1038                    context,
1039                    inline_lambda,
1040                );
1041
1042                if branches.len() == 1 {
1043                    // save a potentially big clone
1044                    // where currently all cases will be 1 branch
1045                    branches[0].traverse_uplc_with_helper(
1046                        scope,
1047                        arg_stack,
1048                        id_gen,
1049                        with,
1050                        context,
1051                        inline_lambda,
1052                    );
1053                } else {
1054                    for branch in branches {
1055                        branch.traverse_uplc_with_helper(
1056                            scope,
1057                            arg_stack.clone(),
1058                            id_gen,
1059                            with,
1060                            context,
1061                            inline_lambda,
1062                        );
1063                    }
1064                }
1065            }
1066            Term::Constr { fields, .. } => {
1067                for field in fields {
1068                    field.traverse_uplc_with_helper(
1069                        scope,
1070                        vec![],
1071                        id_gen,
1072                        with,
1073                        context,
1074                        inline_lambda,
1075                    );
1076                }
1077            }
1078
1079            Term::Builtin(func) => {
1080                let mut args = vec![];
1081
1082                for _ in 0..(func.arity() + usize::try_from(func.force_count()).unwrap()) {
1083                    if let Some(arg) = arg_stack.pop() {
1084                        args.push(arg);
1085                    }
1086                }
1087                // Pass in args up to function arity.
1088                with(None, self, args, scope, context);
1089            }
1090            term => {
1091                with(None, term, vec![], scope, context);
1092            }
1093        }
1094        context.node_count += 1;
1095    }
1096
1097    fn substitute_var(&mut self, original: Rc<Name>, replace_with: &Term<Name>) {
1098        match self {
1099            Term::Var(name) if *name == original => {
1100                *self = replace_with.clone();
1101            }
1102            Term::Delay(body) => Rc::make_mut(body).substitute_var(original, replace_with),
1103            Term::Lambda {
1104                parameter_name,
1105                body,
1106            } if *parameter_name != original => {
1107                Rc::make_mut(body).substitute_var(original, replace_with);
1108            }
1109            Term::Apply { function, argument } => {
1110                Rc::make_mut(function).substitute_var(original.clone(), replace_with);
1111                Rc::make_mut(argument).substitute_var(original, replace_with);
1112            }
1113            Term::Force(f) => {
1114                Rc::make_mut(f).substitute_var(original, replace_with);
1115            }
1116            Term::Case { .. } => todo!(),
1117            Term::Constr { .. } => todo!(),
1118            _ => (),
1119        }
1120    }
1121
1122    fn replace_identity_usage(&mut self, original: Rc<Name>) {
1123        match self {
1124            Term::Delay(body) => {
1125                Rc::make_mut(body).replace_identity_usage(original.clone());
1126            }
1127            Term::Lambda {
1128                parameter_name,
1129                body,
1130            } => {
1131                if *parameter_name != original {
1132                    Rc::make_mut(body).replace_identity_usage(original.clone());
1133                }
1134            }
1135            Term::Apply { function, argument } => {
1136                let func = Rc::make_mut(function);
1137                let arg = Rc::make_mut(argument);
1138
1139                func.replace_identity_usage(original.clone());
1140                arg.replace_identity_usage(original.clone());
1141
1142                let Term::Var(name) = &func else {
1143                    return;
1144                };
1145
1146                if *name == original {
1147                    *self = core::mem::replace(arg, Term::Error.force());
1148                }
1149            }
1150            Term::Force(f) => {
1151                Rc::make_mut(f).replace_identity_usage(original.clone());
1152            }
1153            Term::Case { .. } => todo!(),
1154            Term::Constr { .. } => todo!(),
1155            _ => (),
1156        }
1157    }
1158
1159    fn var_occurrences(
1160        &self,
1161        search_for: Rc<Name>,
1162        mut arg_stack: Vec<()>,
1163        mut force_stack: Vec<()>,
1164    ) -> VarLookup {
1165        match self {
1166            Term::Var(name) => {
1167                if *name == search_for {
1168                    VarLookup::new_found()
1169                } else {
1170                    VarLookup::new()
1171                }
1172            }
1173            Term::Delay(body) => {
1174                let not_forced = usize::from(force_stack.pop().is_none());
1175
1176                body.var_occurrences(search_for, arg_stack, force_stack)
1177                    .delay_if_found(not_forced)
1178            }
1179            Term::Lambda {
1180                parameter_name,
1181                body,
1182            } => {
1183                if parameter_name.text == NO_INLINE {
1184                    body.var_occurrences(search_for, arg_stack, force_stack)
1185                        .no_inline_if_found()
1186                } else if *parameter_name == search_for {
1187                    VarLookup::new()
1188                } else {
1189                    let not_applied = usize::from(arg_stack.pop().is_none());
1190                    body.var_occurrences(search_for, arg_stack, force_stack)
1191                        .delay_if_found(not_applied)
1192                }
1193            }
1194            Term::Apply { function, argument } => {
1195                // unwrap apply and add void to arg stack!
1196                arg_stack.push(());
1197
1198                let apply_var_occurrence_stack = |term: &Term<Name>, arg_stack: Vec<()>| {
1199                    term.var_occurrences(search_for.clone(), arg_stack, force_stack)
1200                };
1201
1202                let apply_var_occurrence_no_stack =
1203                    |term: &Term<Name>| term.var_occurrences(search_for.clone(), vec![], vec![]);
1204
1205                if let Term::Apply {
1206                    function: next_func,
1207                    argument: next_arg,
1208                } = function.as_ref()
1209                {
1210                    // unwrap apply and add void to arg stack!
1211                    arg_stack.push(());
1212                    next_func.carry_args_to_branch(
1213                        next_arg,
1214                        argument,
1215                        arg_stack,
1216                        apply_var_occurrence_stack,
1217                        apply_var_occurrence_no_stack,
1218                    )
1219                } else {
1220                    apply_var_occurrence_stack(function, arg_stack)
1221                        .combine(apply_var_occurrence_no_stack(argument))
1222                }
1223            }
1224            Term::Force(x) => {
1225                force_stack.push(());
1226                x.var_occurrences(search_for, arg_stack, force_stack)
1227            }
1228            Term::Case { .. } => todo!(),
1229            Term::Constr { .. } => todo!(),
1230            _ => VarLookup::new(),
1231        }
1232    }
1233
1234    // This handles the very common case of (if condition then body else error)
1235    // or (if condition then error else body)
1236    // In this case it is fine to treat the body as if it is not delayed
1237    // since the other branch is error
1238    fn carry_args_to_branch(
1239        &self,
1240        then_arg: &Rc<Term<Name>>,
1241        else_arg: &Rc<Term<Name>>,
1242        mut arg_stack: Vec<()>,
1243        var_occurrence_stack: impl FnOnce(&Term<Name>, Vec<()>) -> VarLookup,
1244        var_occurrence_no_stack: impl Fn(&Term<Name>) -> VarLookup,
1245    ) -> VarLookup {
1246        let Term::Apply {
1247            function: builtin,
1248            argument: condition,
1249        } = self
1250        else {
1251            return var_occurrence_stack(self, arg_stack)
1252                .combine(var_occurrence_no_stack(then_arg))
1253                .combine(var_occurrence_no_stack(else_arg));
1254        };
1255
1256        // unwrap apply and add void to arg stack!
1257        arg_stack.push(());
1258
1259        let Term::Delay(else_arg) = else_arg.as_ref() else {
1260            return var_occurrence_stack(builtin, arg_stack)
1261                .combine(var_occurrence_no_stack(condition))
1262                .combine(var_occurrence_no_stack(then_arg))
1263                .combine(var_occurrence_no_stack(else_arg));
1264        };
1265
1266        let Term::Delay(then_arg) = then_arg.as_ref() else {
1267            return var_occurrence_stack(builtin, arg_stack)
1268                .combine(var_occurrence_no_stack(condition))
1269                .combine(var_occurrence_no_stack(then_arg))
1270                .combine(var_occurrence_no_stack(else_arg));
1271        };
1272
1273        match builtin.as_ref() {
1274            Term::Var(a)
1275                if a.text == DefaultFunction::IfThenElse.wrapped_name()
1276                    || a.text == DefaultFunction::ChooseList.wrapped_name() =>
1277            {
1278                if matches!(else_arg.as_ref(), Term::Error) {
1279                    // Pop 3 args of arg_stack due to branch execution
1280                    arg_stack.pop();
1281                    arg_stack.pop();
1282                    arg_stack.pop();
1283
1284                    var_occurrence_no_stack(condition)
1285                        .combine(var_occurrence_stack(then_arg, arg_stack))
1286                } else if matches!(then_arg.as_ref(), Term::Error) {
1287                    // Pop 3 args of arg_stack due to branch execution
1288                    arg_stack.pop();
1289                    arg_stack.pop();
1290                    arg_stack.pop();
1291
1292                    var_occurrence_no_stack(condition)
1293                        .combine(var_occurrence_stack(else_arg, arg_stack))
1294                } else {
1295                    var_occurrence_stack(builtin, arg_stack)
1296                        .combine(var_occurrence_no_stack(condition))
1297                        .combine(var_occurrence_no_stack(then_arg))
1298                        .combine(var_occurrence_no_stack(else_arg))
1299                }
1300            }
1301
1302            _ => var_occurrence_stack(builtin, arg_stack)
1303                .combine(var_occurrence_no_stack(condition))
1304                .combine(var_occurrence_no_stack(then_arg))
1305                .combine(var_occurrence_no_stack(else_arg)),
1306        }
1307    }
1308
1309    fn lambda_reducer(
1310        &mut self,
1311        _id: Option<usize>,
1312        mut arg_stack: Vec<Args>,
1313        _scope: &Scope,
1314        context: &mut Context,
1315    ) -> bool {
1316        let mut changed = false;
1317        match self {
1318            Term::Lambda {
1319                parameter_name,
1320                body,
1321            } => {
1322                // pops stack here no matter what
1323                if let Some(Args::Apply(arg_id, arg_term)) = arg_stack.pop() {
1324                    let replace = match &arg_term {
1325                        // Do nothing for String consts
1326                        Term::Constant(c) if matches!(c.as_ref(), Constant::String(_)) => false,
1327                        // Inline Delay Error terms since total size is only 1 byte
1328                        // So it costs more size to have them hoisted
1329                        Term::Delay(e) if matches!(e.as_ref(), Term::Error) => true,
1330                        // If it wraps a builtin with consts or arguments passed in then inline
1331                        Term::Lambda { .. } => arg_term.is_a_builtin_wrapper(),
1332                        // Inline smaller terms too
1333                        Term::Constant(_) | Term::Var(_) | Term::Builtin(_) => true,
1334
1335                        _ => false,
1336                    };
1337                    changed = replace;
1338
1339                    if replace {
1340                        let body = Rc::make_mut(body);
1341                        context.inlined_apply_ids.push(arg_id);
1342
1343                        body.substitute_var(
1344                            parameter_name.clone(),
1345                            arg_term.pierce_no_inlines_ref(),
1346                        );
1347                        // creates new body that replaces all var occurrences with the arg
1348                        *self = core::mem::replace(body, Term::Error.force());
1349                    }
1350                }
1351            }
1352
1353            Term::Case { .. } => todo!(),
1354            Term::Constr { .. } => todo!(),
1355            _ => (),
1356        };
1357
1358        changed
1359    }
1360
1361    // IMPORTANT: RUNS ONE TIME
1362    fn builtin_force_reducer(
1363        &mut self,
1364        _id: Option<usize>,
1365        mut arg_stack: Vec<Args>,
1366        _scope: &Scope,
1367        context: &mut Context,
1368    ) {
1369        if let Term::Builtin(func) = self {
1370            arg_stack.reverse();
1371            let has_forces = func.force_count() > 0;
1372            while let Some(Args::Force(id)) = arg_stack.pop() {
1373                context.inlined_apply_ids.push(id);
1374            }
1375
1376            if has_forces {
1377                context.builtins_map.insert(*func, ());
1378                *self = Term::var(func.wrapped_name());
1379            }
1380        }
1381    }
1382
1383    // IMPORTANT: RUNS ONE TIME
1384    fn bls381_compressor(
1385        &mut self,
1386        _id: Option<usize>,
1387        _arg_stack: Vec<Args>,
1388        _scope: &Scope,
1389        context: &mut Context,
1390    ) {
1391        if let Term::Constant(con) = self {
1392            match con.as_ref() {
1393                Constant::Bls12_381G1Element(blst_p1) => {
1394                    if let Some(index) = context
1395                        .blst_p1_list
1396                        .iter()
1397                        .position(|item| item == blst_p1.as_ref())
1398                    {
1399                        *self = Term::var(format!("blst_p1_index_{}", index));
1400                    } else {
1401                        context.blst_p1_list.push(*blst_p1.as_ref());
1402                        *self =
1403                            Term::var(format!("blst_p1_index_{}", context.blst_p1_list.len() - 1));
1404                    }
1405                }
1406                Constant::Bls12_381G2Element(blst_p2) => {
1407                    if let Some(index) = context
1408                        .blst_p2_list
1409                        .iter()
1410                        .position(|item| item == blst_p2.as_ref())
1411                    {
1412                        *self = Term::var(format!("blst_p2_index_{}", index));
1413                    } else {
1414                        context.blst_p2_list.push(*blst_p2.as_ref());
1415                        *self =
1416                            Term::var(format!("blst_p2_index_{}", context.blst_p2_list.len() - 1));
1417                    }
1418                }
1419                _ => (),
1420            }
1421        }
1422    }
1423
1424    // The ultimate function when used in conjunction with case_constr_apply
1425    // This splits [lam fun_name [lam fun_name2 rest ..] ..] into
1426    // [[lam fun_name lam fun_name2 rest ..]..] thus
1427    // allowing for some crazy gains from cast_constr_apply_reducer
1428    fn split_body_lambda(&mut self) {
1429        let mut arg_stack = vec![];
1430        let mut current_term = &mut core::mem::replace(self, Term::Error.force());
1431        let mut unsat_lams = vec![];
1432
1433        let mut function_groups = vec![vec![]];
1434        let mut function_dependencies = vec![vec![]];
1435
1436        loop {
1437            match current_term {
1438                Term::Apply { function, argument } => {
1439                    current_term = Rc::make_mut(function);
1440
1441                    let arg = Rc::make_mut(argument);
1442
1443                    arg.split_body_lambda();
1444
1445                    arg_stack.push(Args::Apply(0, core::mem::replace(arg, Term::Error.force())));
1446                }
1447                Term::Lambda {
1448                    parameter_name,
1449                    body,
1450                } => {
1451                    current_term = Rc::make_mut(body);
1452
1453                    if let Some(Args::Apply(_, arg)) = arg_stack.pop() {
1454                        let names = arg.get_var_names();
1455
1456                        let func = (parameter_name.clone(), arg);
1457
1458                        if let Some((position, _)) =
1459                            function_groups.iter().enumerate().rfind(|named_functions| {
1460                                named_functions
1461                                    .1
1462                                    .iter()
1463                                    .any(|(name, _)| names.contains(name))
1464                            })
1465                        {
1466                            let insert_position = position + 1;
1467                            if insert_position == function_groups.len() {
1468                                function_groups.push(vec![func]);
1469                                function_dependencies.push(names);
1470                            } else {
1471                                function_groups[insert_position].push(func);
1472                                function_dependencies[insert_position].extend(names);
1473                            }
1474                        } else {
1475                            function_groups[0].push(func);
1476                            function_dependencies[0].extend(names);
1477                        }
1478                    } else {
1479                        unsat_lams.push(parameter_name.clone());
1480                    }
1481                }
1482                Term::Force(term) => {
1483                    current_term = Rc::make_mut(term);
1484
1485                    arg_stack.push(Args::Force(0));
1486                }
1487                Term::Delay(term) => {
1488                    Rc::make_mut(term).split_body_lambda();
1489                    break;
1490                }
1491                Term::Case { .. } => todo!(),
1492                Term::Constr { .. } => todo!(),
1493                _ => break,
1494            }
1495        }
1496        let mut swap_postions = vec![];
1497
1498        function_groups
1499            .iter()
1500            .enumerate()
1501            .for_each(|(group_index, group)| {
1502                if group.len() <= 3 {
1503                    group
1504                        .iter()
1505                        .enumerate()
1506                        .rev()
1507                        .for_each(|(item_index, (item_name, _))| {
1508                            let current_eligible_position = function_dependencies
1509                                .iter()
1510                                .enumerate()
1511                                .fold_while(group_index, |acc, (new_position, dependencies)| {
1512                                    if dependencies.contains(item_name) {
1513                                        FoldWhile::Done(acc)
1514                                    } else {
1515                                        FoldWhile::Continue(new_position)
1516                                    }
1517                                })
1518                                .into_inner();
1519
1520                            if current_eligible_position > group_index {
1521                                swap_postions.push((
1522                                    group_index,
1523                                    item_index,
1524                                    current_eligible_position,
1525                                ));
1526                            }
1527                        });
1528                }
1529            });
1530
1531        for (group_index, item_index, swap_index) in swap_postions {
1532            let item = function_groups[group_index].remove(item_index);
1533
1534            function_groups[swap_index].push(item);
1535        }
1536
1537        let term_to_build_on = core::mem::replace(current_term, Term::Error.force());
1538
1539        // Replace args that weren't consumed
1540        let term = arg_stack
1541            .into_iter()
1542            .rfold(term_to_build_on, |term, arg| match arg {
1543                Args::Force(_) => term.force(),
1544                Args::Apply(_, arg) => term.apply(arg),
1545            });
1546
1547        let term = function_groups.into_iter().rfold(term, |term, group| {
1548            let term = group.iter().rfold(term, |term, (name, _)| Term::Lambda {
1549                parameter_name: name.clone(),
1550                body: term.into(),
1551            });
1552
1553            group
1554                .into_iter()
1555                .fold(term, |term, (_, arg)| term.apply(arg))
1556        });
1557
1558        let term = unsat_lams
1559            .into_iter()
1560            .rfold(term, |term, name| Term::Lambda {
1561                parameter_name: name.clone(),
1562                body: term.into(),
1563            });
1564
1565        *self = term;
1566    }
1567
1568    fn get_var_names(&self) -> Vec<Rc<Name>> {
1569        let mut names = vec![];
1570
1571        let mut term = self;
1572
1573        loop {
1574            match term {
1575                Term::Apply { function, argument } => {
1576                    let arg_names = argument.get_var_names();
1577
1578                    names.extend(arg_names);
1579
1580                    term = function;
1581                }
1582                Term::Var(name) => {
1583                    names.push(name.clone());
1584                    break;
1585                }
1586                Term::Delay(t) => {
1587                    term = t;
1588                }
1589                Term::Lambda { body, .. } => {
1590                    term = body;
1591                }
1592                Term::Constant(_) | Term::Error | Term::Builtin(_) => {
1593                    break;
1594                }
1595                Term::Force(t) => {
1596                    term = t;
1597                }
1598                Term::Constr { .. } => todo!(),
1599                Term::Case { .. } => todo!(),
1600            }
1601        }
1602
1603        names
1604    }
1605
1606    // IMPORTANT: RUNS ONE TIME AND ONLY ON THE LAST PASS
1607    fn case_constr_apply_reducer(
1608        &mut self,
1609        _id: Option<usize>,
1610        _arg_stack: Vec<Args>,
1611        _scope: &Scope,
1612        _context: &mut Context,
1613    ) {
1614        let mut term = &mut core::mem::replace(self, Term::Error.force());
1615
1616        let mut arg_vec = vec![];
1617
1618        while let Term::Apply { function, argument } = term {
1619            arg_vec.push(Rc::make_mut(argument));
1620
1621            term = Rc::make_mut(function);
1622        }
1623
1624        arg_vec.reverse();
1625
1626        match term {
1627            Term::Case { constr, branches }
1628                if branches.len() == 1 && matches!(constr.as_ref(), Term::Constr { .. }) =>
1629            {
1630                let Term::Constr { fields, .. } = Rc::make_mut(constr) else {
1631                    unreachable!();
1632                };
1633
1634                for arg in arg_vec {
1635                    fields.push(core::mem::replace(arg, Term::Error.force()));
1636                }
1637
1638                *self = core::mem::replace(term, Term::Error.force());
1639            }
1640            _ => {
1641                if arg_vec.len() > 2 {
1642                    let mut fields = vec![];
1643
1644                    for arg in arg_vec {
1645                        fields.push(core::mem::replace(arg, Term::Error.force()));
1646                    }
1647
1648                    *self = Term::constr(0, fields)
1649                        .case(vec![core::mem::replace(term, Term::Error.force())]);
1650                } else {
1651                    for arg in arg_vec {
1652                        *term = (core::mem::replace(term, Term::Error.force()))
1653                            .apply(core::mem::replace(arg, Term::Error.force()));
1654                    }
1655
1656                    *self = core::mem::replace(term, Term::Error.force());
1657                }
1658            }
1659        }
1660    }
1661    // List<Int> in Aiken is actually List<Data<Int>>
1662    // So now we want to convert writeBits arg List<Data<Int>> to List<Int>
1663    // Important: Only runs once and at the end.
1664    fn write_bits_convert_arg(
1665        &mut self,
1666        id: Option<usize>,
1667        mut arg_stack: Vec<Args>,
1668        _scope: &Scope,
1669        context: &mut Context,
1670    ) {
1671        match self {
1672            Term::Apply { argument, .. } => {
1673                let id = id.unwrap();
1674
1675                if context.write_bits_indices_arg.contains(&id) {
1676                    match Rc::make_mut(argument) {
1677                        Term::Constant(constant) => {
1678                            let Constant::ProtoList(tipo, items) = Rc::make_mut(constant) else {
1679                                unreachable!();
1680                            };
1681
1682                            assert!(*tipo == Type::Data);
1683                            *tipo = Type::Integer;
1684
1685                            for item in items {
1686                                let Constant::Data(PlutusData::BigInt(i)) = item else {
1687                                    unreachable!();
1688                                };
1689
1690                                *item = Constant::Integer(from_pallas_bigint(i));
1691                            }
1692                        }
1693                        arg => {
1694                            context.write_bits_convert = true;
1695
1696                            *arg = Term::var(INDICES_CONVERTER)
1697                                .apply(core::mem::replace(arg, Term::Error.force()));
1698                        }
1699                    }
1700                }
1701            }
1702
1703            Term::Builtin(DefaultFunction::WriteBits) => {
1704                if arg_stack.is_empty() {
1705                    context.write_bits_convert = true;
1706
1707                    *self = Term::write_bits()
1708                        .apply(Term::var("__arg_1"))
1709                        .apply(Term::var(INDICES_CONVERTER).apply(Term::var("__arg_2")))
1710                        .apply(Term::var("__arg_3"))
1711                        .lambda("__arg_3")
1712                        .lambda("__arg_2")
1713                        .lambda("__arg_1")
1714                } else {
1715                    // first arg not needed
1716                    arg_stack.pop();
1717
1718                    let Some(Args::Apply(arg_id, _)) = arg_stack.pop() else {
1719                        return;
1720                    };
1721
1722                    context.write_bits_indices_arg.push(arg_id);
1723                }
1724            }
1725            _ => (),
1726        }
1727    }
1728
1729    fn identity_reducer(
1730        &mut self,
1731        _id: Option<usize>,
1732        mut arg_stack: Vec<Args>,
1733        _scope: &Scope,
1734        context: &mut Context,
1735    ) -> bool {
1736        let mut changed = false;
1737
1738        match self {
1739            Term::Lambda {
1740                parameter_name,
1741                body,
1742            } => {
1743                let body = Rc::make_mut(body);
1744                // pops stack here no matter what
1745
1746                let Some(Args::Apply(arg_id, identity_func)) = arg_stack.pop() else {
1747                    return false;
1748                };
1749
1750                let Term::Lambda {
1751                    parameter_name: identity_name,
1752                    body: identity_body,
1753                } = identity_func.pierce_no_inlines()
1754                else {
1755                    return false;
1756                };
1757
1758                let Term::Var(identity_var) = identity_body.as_ref() else {
1759                    return false;
1760                };
1761
1762                if *identity_var == identity_name {
1763                    // Replace all applied usages of identity with the arg
1764                    body.replace_identity_usage(parameter_name.clone());
1765                    // Have to check if the body still has any occurrences of the parameter
1766                    // After attempting replacement
1767                    if !body
1768                        .var_occurrences(parameter_name.clone(), vec![], vec![])
1769                        .found
1770                    {
1771                        changed = true;
1772                        context.inlined_apply_ids.push(arg_id);
1773                        *self = core::mem::replace(body, Term::Error.force());
1774                    }
1775                }
1776            }
1777            Term::Constr { .. } => todo!(),
1778            Term::Case { .. } => todo!(),
1779            _ => (),
1780        };
1781
1782        changed
1783    }
1784
1785    fn inline_reducer(
1786        &mut self,
1787        _id: Option<usize>,
1788        mut arg_stack: Vec<Args>,
1789        _scope: &Scope,
1790        context: &mut Context,
1791    ) -> bool {
1792        let mut changed = false;
1793
1794        match self {
1795            Term::Lambda {
1796                parameter_name,
1797                body,
1798            } => {
1799                // pops stack here no matter what
1800                let Some(Args::Apply(arg_id, arg_term)) = arg_stack.pop() else {
1801                    return false;
1802                };
1803
1804                let arg_term = arg_term.pierce_no_inlines_ref();
1805
1806                let body = Rc::make_mut(body);
1807
1808                let var_lookup = body.var_occurrences(parameter_name.clone(), vec![], vec![]);
1809
1810                let must_execute_condition = var_lookup.delays == 0 && !var_lookup.no_inline;
1811
1812                let cant_throw_condition = matches!(
1813                    arg_term,
1814                    Term::Var(_)
1815                        | Term::Constant(_)
1816                        | Term::Delay(_)
1817                        | Term::Lambda { .. }
1818                        | Term::Builtin(_),
1819                );
1820
1821                let force_wrapped_builtin = context
1822                    .builtins_map
1823                    .keys()
1824                    .any(|b| b.wrapped_name() == parameter_name.text);
1825
1826                // This will inline terms that only occur once
1827                // if they are guaranteed to execute or can't throw an error by themselves
1828                if !force_wrapped_builtin
1829                    && var_lookup.occurrences == 1
1830                    && (must_execute_condition || cant_throw_condition)
1831                {
1832                    changed = true;
1833                    body.substitute_var(parameter_name.clone(), arg_term);
1834
1835                    context.inlined_apply_ids.push(arg_id);
1836                    *self = core::mem::replace(body, Term::Error.force());
1837
1838                // This will strip out unused terms that can't throw an error by themselves
1839                } else if !var_lookup.found && (cant_throw_condition || force_wrapped_builtin) {
1840                    changed = true;
1841                    context.inlined_apply_ids.push(arg_id);
1842                    *self = core::mem::replace(body, Term::Error.force());
1843                }
1844            }
1845
1846            Term::Constr { .. } => todo!(),
1847            Term::Case { .. } => todo!(),
1848            _ => {}
1849        };
1850        changed
1851    }
1852
1853    fn force_delay_reducer(
1854        &mut self,
1855        _id: Option<usize>,
1856        mut arg_stack: Vec<Args>,
1857        _scope: &Scope,
1858        context: &mut Context,
1859    ) -> bool {
1860        let mut changed = false;
1861        if let Term::Delay(d) = self {
1862            if let Some(Args::Force(id)) = arg_stack.pop() {
1863                changed = true;
1864                context.inlined_apply_ids.push(id);
1865                *self = core::mem::replace(Rc::make_mut(d), Term::Error.force())
1866            } else if let Term::Force(var) = d.as_ref() {
1867                if let Term::Var(_) = var.as_ref() {
1868                    changed = true;
1869                    *self = var.as_ref().clone();
1870                }
1871            }
1872        }
1873        changed
1874    }
1875
1876    fn remove_no_inlines(
1877        &mut self,
1878        _id: Option<usize>,
1879        _arg_stack: Vec<Args>,
1880        _scope: &Scope,
1881        _context: &mut Context,
1882    ) {
1883        match self {
1884            Term::Lambda {
1885                parameter_name,
1886                body,
1887            } if parameter_name.text == NO_INLINE => {
1888                *self = core::mem::replace(Rc::make_mut(body), Term::Error.force());
1889            }
1890            _ => (),
1891        }
1892    }
1893
1894    // IMPORTANT: RUNS ONE TIME
1895    fn inline_constr_ops(
1896        &mut self,
1897        _id: Option<usize>,
1898        _arg_stack: Vec<Args>,
1899        _scope: &Scope,
1900        _context: &mut Context,
1901    ) {
1902        if let Term::Apply { function, argument } = self {
1903            if let Term::Var(name) = function.as_ref() {
1904                let arg = Rc::make_mut(argument);
1905                if name.text == CONSTR_FIELDS_EXPOSER {
1906                    *self = Term::snd_pair().apply(
1907                        Term::unconstr_data().apply(core::mem::replace(arg, Term::Error.force())),
1908                    )
1909                } else if name.text == CONSTR_INDEX_EXPOSER {
1910                    *self = Term::fst_pair().apply(
1911                        Term::unconstr_data().apply(core::mem::replace(arg, Term::Error.force())),
1912                    )
1913                }
1914            } else if let Term::Lambda {
1915                parameter_name,
1916                body,
1917            } = Rc::make_mut(function)
1918            {
1919                if parameter_name.text == CONSTR_INDEX_EXPOSER
1920                    || parameter_name.text == CONSTR_FIELDS_EXPOSER
1921                {
1922                    let body = Rc::make_mut(body);
1923                    *self = core::mem::replace(body, Term::Error)
1924                }
1925            }
1926        }
1927    }
1928
1929    fn cast_data_reducer(
1930        &mut self,
1931        _id: Option<usize>,
1932        mut arg_stack: Vec<Args>,
1933        _scope: &Scope,
1934        context: &mut Context,
1935    ) -> bool {
1936        let mut changed = false;
1937
1938        match self {
1939            Term::Builtin(first_function) => {
1940                let Some(Args::Apply(arg_id, mut arg_term)) = arg_stack.pop() else {
1941                    return false;
1942                };
1943
1944                match &mut arg_term {
1945                    Term::Apply { function, argument } => {
1946                        if let Term::Builtin(second_function) = function.as_ref() {
1947                            match (first_function, second_function) {
1948                                (DefaultFunction::UnIData, DefaultFunction::IData)
1949                                | (DefaultFunction::IData, DefaultFunction::UnIData)
1950                                | (DefaultFunction::BData, DefaultFunction::UnBData)
1951                                | (DefaultFunction::UnBData, DefaultFunction::BData)
1952                                | (DefaultFunction::ListData, DefaultFunction::UnListData)
1953                                | (DefaultFunction::UnListData, DefaultFunction::ListData)
1954                                | (DefaultFunction::MapData, DefaultFunction::UnMapData)
1955                                | (DefaultFunction::UnMapData, DefaultFunction::MapData) => {
1956                                    changed = true;
1957                                    context.inlined_apply_ids.push(arg_id);
1958                                    *self = core::mem::replace(
1959                                        Rc::make_mut(argument),
1960                                        Term::Error.force(),
1961                                    );
1962                                }
1963                                _ => {}
1964                            }
1965                        }
1966                    }
1967                    Term::Constant(c) => match (first_function, c.as_ref()) {
1968                        (
1969                            DefaultFunction::UnIData,
1970                            Constant::Data(PlutusData::BigInt(BigInt::Int(i))),
1971                        ) => {
1972                            changed = true;
1973                            context.inlined_apply_ids.push(arg_id);
1974                            *self = Term::integer(i128::from(*i).into());
1975                        }
1976                        (DefaultFunction::IData, Constant::Integer(i)) => {
1977                            changed = true;
1978                            context.inlined_apply_ids.push(arg_id);
1979                            *self = Term::data(Data::integer(i.clone()));
1980                        }
1981                        (DefaultFunction::UnBData, Constant::Data(PlutusData::BoundedBytes(b))) => {
1982                            changed = true;
1983                            context.inlined_apply_ids.push(arg_id);
1984                            *self = Term::byte_string(b.clone().into());
1985                        }
1986                        (DefaultFunction::BData, Constant::ByteString(b)) => {
1987                            changed = true;
1988                            context.inlined_apply_ids.push(arg_id);
1989                            *self = Term::data(Data::bytestring(b.clone()));
1990                        }
1991                        (DefaultFunction::UnListData, Constant::Data(PlutusData::Array(l))) => {
1992                            changed = true;
1993                            context.inlined_apply_ids.push(arg_id);
1994                            *self = Term::list_values(
1995                                l.iter()
1996                                    .map(|item| Constant::Data(item.clone()))
1997                                    .collect_vec(),
1998                            );
1999                        }
2000                        (DefaultFunction::ListData, Constant::ProtoList(_, l)) => {
2001                            changed = true;
2002                            context.inlined_apply_ids.push(arg_id);
2003                            *self = Term::data(Data::list(
2004                                l.iter()
2005                                    .map(|item| match item {
2006                                        Constant::Data(d) => d.clone(),
2007                                        _ => unreachable!(),
2008                                    })
2009                                    .collect_vec(),
2010                            ));
2011                        }
2012                        (DefaultFunction::MapData, Constant::ProtoList(_, m)) => {
2013                            changed = true;
2014                            context.inlined_apply_ids.push(arg_id);
2015                            *self = Term::data(Data::map(
2016                                m.iter()
2017                                    .map(|m| match m {
2018                                        Constant::ProtoPair(_, _, f, s) => {
2019                                            match (f.as_ref(), s.as_ref()) {
2020                                                (Constant::Data(d), Constant::Data(d2)) => {
2021                                                    (d.clone(), d2.clone())
2022                                                }
2023                                                _ => unreachable!(),
2024                                            }
2025                                        }
2026                                        _ => unreachable!(),
2027                                    })
2028                                    .collect_vec(),
2029                            ));
2030                        }
2031                        (DefaultFunction::UnMapData, Constant::Data(PlutusData::Map(m))) => {
2032                            changed = true;
2033                            context.inlined_apply_ids.push(arg_id);
2034                            *self = Term::map_values(
2035                                m.iter()
2036                                    .map(|item| {
2037                                        Constant::ProtoPair(
2038                                            Type::Data,
2039                                            Type::Data,
2040                                            Constant::Data(item.0.clone()).into(),
2041                                            Constant::Data(item.1.clone()).into(),
2042                                        )
2043                                    })
2044                                    .collect_vec(),
2045                            );
2046                        }
2047                        _ => {}
2048                    },
2049                    _ => {}
2050                }
2051            }
2052            Term::Constr { .. } => todo!(),
2053            Term::Case { .. } => todo!(),
2054            _ => {}
2055        }
2056        changed
2057    }
2058
2059    // Converts subtract integer with a constant to add integer with a negative constant
2060    fn convert_arithmetic_ops(
2061        &mut self,
2062        _id: Option<usize>,
2063        arg_stack: Vec<Args>,
2064        _scope: &Scope,
2065        context: &mut Context,
2066    ) -> bool {
2067        let mut changed = false;
2068        match self {
2069            Term::Builtin(d @ DefaultFunction::SubtractInteger) => {
2070                if arg_stack.len() == d.arity() {
2071                    let Some(Args::Apply(apply_id, Term::Constant(_))) = arg_stack.last() else {
2072                        return false;
2073                    };
2074                    changed = true;
2075                    context.constants_to_flip.push(*apply_id);
2076
2077                    *self = Term::Builtin(DefaultFunction::AddInteger);
2078                }
2079            }
2080            Term::Constr { .. } => todo!(),
2081            Term::Case { .. } => todo!(),
2082            _ => {}
2083        }
2084        changed
2085    }
2086
2087    fn builtin_eval_reducer(
2088        &mut self,
2089        _id: Option<usize>,
2090        mut arg_stack: Vec<Args>,
2091        _scope: &Scope,
2092        context: &mut Context,
2093    ) -> bool {
2094        let mut changed = false;
2095
2096        match self {
2097            Term::Builtin(func) => {
2098                arg_stack = arg_stack
2099                    .into_iter()
2100                    .filter(|args| matches!(args, Args::Apply(_, _)))
2101                    .collect_vec();
2102
2103                let args = arg_stack
2104                    .iter()
2105                    .map(|args| {
2106                        let Args::Apply(_, term) = args else {
2107                            unreachable!()
2108                        };
2109
2110                        term.pierce_no_inlines_ref()
2111                    })
2112                    .collect_vec();
2113                if arg_stack.len() == func.arity() && func.is_error_safe(&args) {
2114                    changed = true;
2115                    let applied_term =
2116                        arg_stack
2117                            .into_iter()
2118                            .fold(Term::Builtin(*func), |acc, item| {
2119                                let Args::Apply(arg_id, arg) = item else {
2120                                    unreachable!()
2121                                };
2122
2123                                context.inlined_apply_ids.push(arg_id);
2124                                acc.apply(arg.pierce_no_inlines().clone())
2125                            });
2126
2127                    // The check above is to make sure the program is error safe
2128                    let eval_term: Term<Name> = Program {
2129                        version: (1, 0, 0),
2130                        term: applied_term,
2131                    }
2132                    .to_named_debruijn()
2133                    .unwrap()
2134                    .eval(ExBudget::default())
2135                    .result()
2136                    .unwrap()
2137                    .try_into()
2138                    .unwrap();
2139
2140                    *self = eval_term;
2141                }
2142            }
2143            Term::Constr { .. } => todo!(),
2144            Term::Case { .. } => todo!(),
2145            _ => (),
2146        }
2147        changed
2148    }
2149
2150    fn remove_inlined_ids(
2151        &mut self,
2152        id: Option<usize>,
2153        _arg_stack: Vec<Args>,
2154        _scope: &Scope,
2155        context: &mut Context,
2156    ) {
2157        match self {
2158            Term::Apply { function, .. } | Term::Force(function) => {
2159                // We inlined the arg so now we remove the application of it
2160                let Some(id) = id else {
2161                    return;
2162                };
2163
2164                if context.inlined_apply_ids.contains(&id) {
2165                    let func = Rc::make_mut(function);
2166                    *self = core::mem::replace(func, Term::Error.force());
2167                }
2168            }
2169            _ => (),
2170        }
2171    }
2172
2173    fn flip_constants(
2174        &mut self,
2175        id: Option<usize>,
2176        _arg_stack: Vec<Args>,
2177        _scope: &Scope,
2178        context: &mut Context,
2179    ) {
2180        if let Term::Apply { argument, .. } = self {
2181            let Some(id) = id else {
2182                return;
2183            };
2184
2185            if context.constants_to_flip.contains(&id) {
2186                let Term::Constant(c) = Rc::make_mut(argument) else {
2187                    unreachable!();
2188                };
2189
2190                let Constant::Integer(i) = c.as_ref() else {
2191                    unreachable!();
2192                };
2193
2194                *c = Constant::Integer(i.neg()).into();
2195            }
2196        }
2197    }
2198
2199    pub fn pierce_no_inlines_ref(&self) -> &Self {
2200        let mut term = self;
2201
2202        while let Term::Lambda {
2203            parameter_name,
2204            body,
2205        } = term
2206        {
2207            if parameter_name.as_ref().text == NO_INLINE {
2208                term = body;
2209            } else {
2210                break;
2211            }
2212        }
2213
2214        term
2215    }
2216
2217    fn pierce_no_inlines(mut self) -> Self {
2218        let term = &mut self;
2219
2220        while let Term::Lambda {
2221            parameter_name,
2222            body,
2223        } = term
2224        {
2225            if parameter_name.as_ref().text == NO_INLINE {
2226                *term = core::mem::replace(Rc::make_mut(body), Term::Error.force());
2227            } else {
2228                break;
2229            }
2230        }
2231
2232        core::mem::replace(term, Term::Error.force())
2233    }
2234
2235    fn is_a_builtin_wrapper(&self) -> bool {
2236        let (names, term) = self.pop_lambdas_and_get_names();
2237
2238        let mut arg_names = vec![];
2239
2240        let mut term = term;
2241
2242        while let Term::Apply { function, argument } = term {
2243            match argument.as_ref() {
2244                Term::Var(name) => arg_names.push(format!("{}_{}", name.text, name.unique)),
2245
2246                Term::Constant(_) => {}
2247                _ => {
2248                    //Break loop, it's not a builtin wrapper function
2249                    return false;
2250                }
2251            }
2252            term = function.as_ref();
2253        }
2254
2255        let func_is_builtin = match term {
2256            Term::Var(name) => forceable_wrapped_names().contains(&name.text),
2257            Term::Builtin(_) => true,
2258            _ => false,
2259        };
2260
2261        arg_names.iter().all(|item| names.contains(item)) && func_is_builtin
2262    }
2263
2264    fn pop_lambdas_and_get_names(&self) -> (Vec<String>, &Term<Name>) {
2265        let mut names = vec![];
2266
2267        let mut term = self;
2268
2269        while let Term::Lambda {
2270            parameter_name,
2271            body,
2272        } = term
2273        {
2274            if parameter_name.text != NO_INLINE {
2275                names.push(format!("{}_{}", parameter_name.text, parameter_name.unique));
2276            }
2277            term = body.as_ref();
2278        }
2279
2280        (names, term)
2281    }
2282}
2283
2284impl Program<Name> {
2285    fn traverse_uplc_with(
2286        self,
2287        inline_lambda: bool,
2288        with: &mut impl FnMut(Option<usize>, &mut Term<Name>, Vec<Args>, &Scope, &mut Context),
2289    ) -> (Self, Context) {
2290        let mut term = self.term;
2291        let scope = Scope { scope: vec![] };
2292        let arg_stack = vec![];
2293        let mut id_gen = IdGen::new();
2294
2295        let mut context = Context {
2296            inlined_apply_ids: vec![],
2297            constants_to_flip: vec![],
2298            write_bits_indices_arg: vec![],
2299            builtins_map: HashMap::new(),
2300            blst_p1_list: vec![],
2301            blst_p2_list: vec![],
2302            write_bits_convert: false,
2303            node_count: 0,
2304        };
2305
2306        term.traverse_uplc_with_helper(
2307            &scope,
2308            arg_stack,
2309            &mut id_gen,
2310            with,
2311            &mut context,
2312            inline_lambda,
2313        );
2314        (
2315            Program {
2316                version: self.version,
2317                term,
2318            },
2319            context,
2320        )
2321    }
2322    // This runs the optimizations that are only done a single time
2323    pub fn run_once_pass(self) -> Self {
2324        // First pass is necessary to ensure fst_pair and snd_pair are inlined before
2325        // builtin_force_reducer is run
2326        let (program, context) = self
2327            .traverse_uplc_with(false, &mut |id, term, _arg_stack, scope, context| {
2328                term.inline_constr_ops(id, vec![], scope, context);
2329            })
2330            .0
2331            .traverse_uplc_with(false, &mut |id, term, arg_stack, scope, context| {
2332                term.bls381_compressor(id, vec![], scope, context);
2333                term.builtin_force_reducer(id, arg_stack, scope, context);
2334                term.remove_inlined_ids(id, vec![], scope, context);
2335            });
2336
2337        let mut term = program.term;
2338
2339        for (index, blst_p1) in context.blst_p1_list.into_iter().enumerate() {
2340            let compressed = blst_p1.compress();
2341
2342            term = term
2343                .lambda(format!("blst_p1_index_{}", index))
2344                .apply(Term::bls12_381_g1_uncompress().apply(Term::byte_string(compressed)));
2345        }
2346
2347        for (index, blst_p2) in context.blst_p2_list.into_iter().enumerate() {
2348            let compressed = blst_p2.compress();
2349
2350            term = term
2351                .lambda(format!("blst_p2_index_{}", index))
2352                .apply(Term::bls12_381_g2_uncompress().apply(Term::byte_string(compressed)));
2353        }
2354
2355        for default_func in context.builtins_map.keys().sorted().cloned() {
2356            term = term.lambda(default_func.wrapped_name());
2357        }
2358
2359        for default_func in context.builtins_map.keys().sorted().cloned().rev() {
2360            term = term.apply(if default_func.force_count() == 1 {
2361                Term::Builtin(default_func).force()
2362            } else {
2363                Term::Builtin(default_func).force().force()
2364            });
2365        }
2366
2367        let mut program = Program {
2368            version: program.version,
2369            term,
2370        };
2371
2372        let mut interner = CodeGenInterner::new();
2373
2374        interner.program(&mut program);
2375
2376        let program = Program::<NamedDeBruijn>::try_from(program).unwrap();
2377
2378        Program::<Name>::try_from(program).unwrap()
2379    }
2380
2381    pub fn multi_pass(self) -> (Self, Context) {
2382        self.traverse_uplc_with(true, &mut |id, term, arg_stack, scope, context| {
2383            let false = term.lambda_reducer(id, arg_stack.clone(), scope, context) else {
2384                term.remove_inlined_ids(id, vec![], scope, context);
2385                return;
2386            };
2387
2388            let false = term.identity_reducer(id, arg_stack.clone(), scope, context) else {
2389                term.remove_inlined_ids(id, vec![], scope, context);
2390                return;
2391            };
2392
2393            let false = term.inline_reducer(id, arg_stack.clone(), scope, context) else {
2394                term.remove_inlined_ids(id, vec![], scope, context);
2395                return;
2396            };
2397
2398            let false = term.force_delay_reducer(id, arg_stack.clone(), scope, context) else {
2399                term.remove_inlined_ids(id, vec![], scope, context);
2400                return;
2401            };
2402
2403            let false = term.cast_data_reducer(id, arg_stack.clone(), scope, context) else {
2404                term.remove_inlined_ids(id, vec![], scope, context);
2405                return;
2406            };
2407
2408            let false = term.builtin_eval_reducer(id, arg_stack.clone(), scope, context) else {
2409                term.remove_inlined_ids(id, vec![], scope, context);
2410                return;
2411            };
2412
2413            term.convert_arithmetic_ops(id, arg_stack, scope, context);
2414            term.flip_constants(id, vec![], scope, context);
2415            term.remove_inlined_ids(id, vec![], scope, context);
2416        })
2417    }
2418
2419    pub fn run_one_opt(
2420        self,
2421        inline_lambda: bool,
2422        with: &mut impl FnMut(Option<usize>, &mut Term<Name>, Vec<Args>, &Scope, &mut Context),
2423    ) -> Self {
2424        let (mut program, context) =
2425            self.traverse_uplc_with(inline_lambda, &mut |id, term, arg_stack, scope, context| {
2426                with(id, term, arg_stack, scope, context);
2427                term.flip_constants(id, vec![], scope, context);
2428                term.remove_inlined_ids(id, vec![], scope, context);
2429            });
2430
2431        if context.write_bits_convert {
2432            program.term = program.term.data_list_to_integer_list();
2433        }
2434
2435        program
2436    }
2437
2438    pub fn clean_up_no_inlines(self) -> Self {
2439        self.traverse_uplc_with(true, &mut |id, term, _arg_stack, scope, context| {
2440            term.remove_no_inlines(id, vec![], scope, context);
2441        })
2442        .0
2443    }
2444
2445    pub fn afterwards(self) -> Self {
2446        let (mut program, context) =
2447            self.traverse_uplc_with(true, &mut |id, term, arg_stack, scope, context| {
2448                term.write_bits_convert_arg(id, arg_stack, scope, context);
2449            });
2450
2451        program = program
2452            .split_body_lambda_reducer()
2453            .traverse_uplc_with(true, &mut |id, term, _arg_stack, scope, context| {
2454                term.case_constr_apply_reducer(id, vec![], scope, context);
2455            })
2456            .0;
2457
2458        if context.write_bits_convert {
2459            program.term = program.term.data_list_to_integer_list();
2460        }
2461
2462        let mut interner = CodeGenInterner::new();
2463
2464        interner.program(&mut program);
2465
2466        let program = Program::<NamedDeBruijn>::try_from(program).unwrap();
2467
2468        Program::<Name>::try_from(program).unwrap()
2469    }
2470
2471    // This one doesn't use the context since it's complicated and traverses the ast twice
2472    pub fn builtin_curry_reducer(self) -> Self {
2473        let mut curried_terms = vec![];
2474        let mut id_mapped_curry_terms: HashMap<CurriedName, (Scope, Term<Name>, usize)> =
2475            HashMap::new();
2476        let mut curry_applied_ids = vec![];
2477        let mut scope_mapped_to_term: HashMap<Scope, Vec<(CurriedName, Term<Name>)>> =
2478            HashMap::new();
2479
2480        let mut flipped_terms: HashMap<Scope, bool> = HashMap::new();
2481
2482        let mut final_ids: HashMap<Vec<usize>, ()> = HashMap::new();
2483
2484        let (step_a, _) = self.traverse_uplc_with(
2485            false,
2486            &mut |_id, term, arg_stack, scope, _context| match term {
2487                Term::Builtin(func) => {
2488                    if func.can_curry_builtin() && arg_stack.len() == func.arity() {
2489                        let arg_stack = arg_stack
2490                            .into_iter()
2491                            .map(|item| {
2492                                let Args::Apply(arg_id, arg) = item else {
2493                                    unreachable!()
2494                                };
2495                                (arg_id, arg)
2496                            })
2497                            .collect_vec();
2498                        // In the case of order agnostic builtins we want to sort the args by constant first
2499                        // This gives us the opportunity to curry constants that often pop up in the code
2500
2501                        let builtin_args = BuiltinArgs::args_from_arg_stack(arg_stack, *func);
2502
2503                        // First we see if we have already curried this builtin before
2504                        let mut id_vec = if let Some((index, _)) =
2505                            curried_terms.iter_mut().find_position(
2506                                |curried_term: &&mut CurriedBuiltin| curried_term.func == *func,
2507                            ) {
2508                            // We found it the builtin was curried before
2509                            // So now we merge the new args into the existing curried builtin
2510                            let curried_builtin = curried_terms.swap_remove(index);
2511
2512                            let curried_builtin =
2513                                curried_builtin.merge_node_by_path(builtin_args.clone());
2514
2515                            flipped_terms
2516                                .insert(scope.clone(), curried_builtin.is_flipped(&builtin_args));
2517
2518                            let Some(id_vec) = curried_builtin.get_id_args(builtin_args) else {
2519                                unreachable!();
2520                            };
2521
2522                            curried_terms.push(curried_builtin);
2523
2524                            id_vec
2525                        } else {
2526                            // Brand new builtin so we add it to the list
2527                            let curried_builtin = builtin_args.clone().args_to_curried_args(*func);
2528
2529                            let Some(id_vec) = curried_builtin.get_id_args(builtin_args) else {
2530                                unreachable!();
2531                            };
2532
2533                            curried_terms.push(curried_builtin);
2534
2535                            id_vec
2536                        };
2537
2538                        while let Some(node) = id_vec.pop() {
2539                            let mut id_only_vec =
2540                                id_vec.iter().map(|item| item.curried_id).collect_vec();
2541
2542                            id_only_vec.push(node.curried_id);
2543
2544                            let curry_name = CurriedName {
2545                                func_name: func.aiken_name(),
2546                                id_vec: id_only_vec,
2547                            };
2548
2549                            if let Some((map_scope, _, occurrences)) =
2550                                id_mapped_curry_terms.get_mut(&curry_name)
2551                            {
2552                                *map_scope = map_scope.common_ancestor(scope);
2553                                *occurrences += 1;
2554                            } else if id_vec.is_empty() {
2555                                id_mapped_curry_terms.insert(
2556                                    curry_name,
2557                                    (scope.clone(), Term::Builtin(*func).apply(node.term), 1),
2558                                );
2559                            } else {
2560                                let var_name = id_vec_function_to_var(
2561                                    &func.aiken_name(),
2562                                    &id_vec.iter().map(|item| item.curried_id).collect_vec(),
2563                                );
2564
2565                                id_mapped_curry_terms.insert(
2566                                    curry_name,
2567                                    (scope.clone(), Term::var(var_name).apply(node.term), 1),
2568                                );
2569                            }
2570                        }
2571                    }
2572                }
2573                Term::Constr { .. } => todo!(),
2574                Term::Case { .. } => todo!(),
2575                _ => {}
2576            },
2577        );
2578
2579        id_mapped_curry_terms
2580            .into_iter()
2581            // Only hoist for occurrences greater than 2
2582            .filter(|(_, (_, _, occurrences))| *occurrences > 2)
2583            .for_each(|(key, val)| {
2584                final_ids.insert(key.id_vec.clone(), ());
2585
2586                match scope_mapped_to_term.get_mut(&val.0) {
2587                    Some(list) => {
2588                        let insert_position = list
2589                            .iter()
2590                            .position(|(list_key, _)| key.len() <= list_key.len())
2591                            .unwrap_or(list.len());
2592
2593                        list.insert(insert_position, (key, val.1));
2594                    }
2595                    None => {
2596                        scope_mapped_to_term.insert(val.0, vec![(key, val.1)]);
2597                    }
2598                }
2599            });
2600
2601        let (mut step_b, _) = step_a.traverse_uplc_with(
2602            false,
2603            &mut |id, term, arg_stack, scope, _context| match term {
2604                Term::Builtin(func) => {
2605                    if func.can_curry_builtin() && arg_stack.len() == func.arity() {
2606                        let mut arg_stack = arg_stack
2607                            .into_iter()
2608                            .map(|item| {
2609                                let Args::Apply(arg_id, arg) = item else {
2610                                    unreachable!()
2611                                };
2612                                (arg_id, arg)
2613                            })
2614                            .collect_vec();
2615
2616                        let Some(curried_builtin) =
2617                            curried_terms.iter().find(|curry| curry.func == *func)
2618                        else {
2619                            return;
2620                        };
2621
2622                        if let Some(true) = flipped_terms.get(scope) {
2623                            arg_stack.reverse();
2624                        }
2625
2626                        let builtin_args = BuiltinArgs::args_from_arg_stack(arg_stack, *func);
2627
2628                        let Some(mut id_vec) = curried_builtin.get_id_args(builtin_args) else {
2629                            return;
2630                        };
2631
2632                        while !id_vec.is_empty() {
2633                            let id_lookup = id_vec.iter().map(|item| item.curried_id).collect_vec();
2634
2635                            if final_ids.contains_key(&id_lookup) {
2636                                break;
2637                            }
2638                            id_vec.pop();
2639                        }
2640
2641                        if id_vec.is_empty() {
2642                            return;
2643                        }
2644
2645                        let name = id_vec_function_to_var(
2646                            &func.aiken_name(),
2647                            &id_vec.iter().map(|item| item.curried_id).collect_vec(),
2648                        );
2649
2650                        id_vec.iter().for_each(|item| {
2651                            curry_applied_ids.push(item.applied_id);
2652                        });
2653
2654                        *term = Term::var(name);
2655                    }
2656                }
2657                Term::Apply { function, .. } => {
2658                    let id = id.unwrap();
2659
2660                    if curry_applied_ids.contains(&id) {
2661                        *term = function.as_ref().clone();
2662                    }
2663
2664                    if let Some(insert_list) = scope_mapped_to_term.remove(scope) {
2665                        for (key, val) in insert_list.into_iter().rev() {
2666                            let name = id_vec_function_to_var(&key.func_name, &key.id_vec);
2667
2668                            if term
2669                                .var_occurrences(Name::text(&name).into(), vec![], vec![])
2670                                .found
2671                            {
2672                                *term = term.clone().lambda(name).apply(val);
2673                            }
2674                        }
2675                    }
2676                }
2677                Term::Constr { .. } => todo!(),
2678                Term::Case { .. } => todo!(),
2679                _ => {
2680                    if let Some(insert_list) = scope_mapped_to_term.remove(scope) {
2681                        for (key, val) in insert_list.into_iter().rev() {
2682                            let name = id_vec_function_to_var(&key.func_name, &key.id_vec);
2683
2684                            if term
2685                                .var_occurrences(Name::text(&name).into(), vec![], vec![])
2686                                .found
2687                            {
2688                                *term = term.clone().lambda(name).apply(val);
2689                            }
2690                        }
2691                    }
2692                }
2693            },
2694        );
2695
2696        let mut interner = CodeGenInterner::new();
2697
2698        interner.program(&mut step_b);
2699
2700        step_b
2701    }
2702
2703    pub fn split_body_lambda_reducer(mut self) -> Self {
2704        self.term.split_body_lambda();
2705
2706        self
2707    }
2708}
2709
2710fn id_vec_function_to_var(func_name: &str, id_vec: &[usize]) -> String {
2711    format!(
2712        "__{}_{}_curried",
2713        func_name,
2714        id_vec
2715            .iter()
2716            .map(|item| item.to_string())
2717            .collect::<Vec<String>>()
2718            .join("_")
2719    )
2720}
2721
2722#[cfg(test)]
2723mod tests {
2724    use super::NO_INLINE;
2725    use crate::pallas_primitives::conway::{BigInt, PlutusData};
2726    use crate::uplc::{
2727        ast::{Constant, Data, Name, NamedDeBruijn, Program, Term},
2728        builder::{CONSTR_FIELDS_EXPOSER, CONSTR_INDEX_EXPOSER},
2729        builtins::DefaultFunction,
2730        optimize::interner::CodeGenInterner,
2731    };
2732    use pretty_assertions::assert_eq;
2733
2734    fn compare_optimization(
2735        mut expected: Program<Name>,
2736        mut program: Program<Name>,
2737        optimization: fn(Program<Name>) -> Program<Name>,
2738    ) {
2739        let mut interner = CodeGenInterner::new();
2740
2741        interner.program(&mut program);
2742
2743        let mut interner = CodeGenInterner::new();
2744
2745        interner.program(&mut expected);
2746
2747        let expected: Program<NamedDeBruijn> = expected.try_into().unwrap();
2748        let actual = optimization(program);
2749
2750        let actual: Program<NamedDeBruijn> = actual.try_into().unwrap();
2751
2752        assert_eq!(actual, expected);
2753    }
2754
2755    #[test]
2756    fn lambda_reduce_var() {
2757        let program = Program {
2758            version: (1, 0, 0),
2759            term: Term::var("bar")
2760                .lambda("bar")
2761                .apply(Term::var("foo"))
2762                .lambda("foo")
2763                .apply(
2764                    Term::constr_data()
2765                        .apply(Term::integer(3.into()))
2766                        .apply(Term::list_values(vec![])),
2767                ),
2768        };
2769
2770        let expected = Program {
2771            version: (1, 0, 0),
2772            term: Term::var("foo").lambda("foo").apply(
2773                Term::constr_data()
2774                    .apply(Term::integer(3.into()))
2775                    .apply(Term::list_values(vec![])),
2776            ),
2777        };
2778
2779        compare_optimization(expected, program, |p| {
2780            p.run_one_opt(true, &mut |id, term, arg_stack, scope, context| {
2781                term.lambda_reducer(id, arg_stack, scope, context);
2782            })
2783        });
2784    }
2785
2786    #[test]
2787    fn lambda_reduce_constant() {
2788        let program = Program {
2789            version: (1, 0, 0),
2790            term: Term::var("foo")
2791                .lambda("foo")
2792                .apply(Term::integer(6.into())),
2793        };
2794
2795        let expected: Program<Name> = Program {
2796            version: (1, 0, 0),
2797            term: Term::integer(6.into()),
2798        };
2799
2800        compare_optimization(expected, program, |p| {
2801            p.run_one_opt(true, &mut |id, term, arg_stack, scope, context| {
2802                term.lambda_reducer(id, arg_stack, scope, context);
2803            })
2804        });
2805    }
2806
2807    #[test]
2808    fn lambda_reduce_builtin() {
2809        let program = Program {
2810            version: (1, 0, 0),
2811            term: Term::var("foo").lambda("foo").apply(Term::add_integer()),
2812        };
2813
2814        let expected: Program<Name> = Program {
2815            version: (1, 0, 0),
2816            term: Term::add_integer(),
2817        };
2818
2819        compare_optimization(expected, program, |p| {
2820            p.run_one_opt(true, &mut |id, term, arg_stack, scope, context| {
2821                term.lambda_reducer(id, arg_stack, scope, context);
2822            })
2823        });
2824    }
2825
2826    #[test]
2827    fn lambda_reduce_force_delay_error_lam() {
2828        let program: Program<Name> = Program {
2829            version: (1, 0, 0),
2830            term: Term::var("foo")
2831                .apply(Term::var("bar"))
2832                .apply(Term::var("baz"))
2833                .apply(Term::var("bat"))
2834                .lambda("foo")
2835                .apply(Term::snd_pair())
2836                .lambda("bar")
2837                .apply(Term::integer(1.into()).delay())
2838                .lambda("baz")
2839                .apply(Term::Error)
2840                .lambda("bat")
2841                .apply(Term::bool(false).lambda("x")),
2842        };
2843
2844        let expected = Program {
2845            version: (1, 0, 0),
2846            term: Term::var("foo")
2847                .apply(Term::var("bar"))
2848                .apply(Term::var("baz"))
2849                .apply(Term::var("bat"))
2850                .lambda("foo")
2851                .apply(Term::snd_pair())
2852                .lambda("bar")
2853                .apply(Term::integer(1.into()).delay())
2854                .lambda("baz")
2855                .apply(Term::Error)
2856                .lambda("bat")
2857                .apply(Term::bool(false).lambda("x")),
2858        };
2859
2860        compare_optimization(expected, program, |p| {
2861            p.run_one_opt(true, &mut |id, term, arg_stack, scope, context| {
2862                term.lambda_reducer(id, arg_stack, scope, context);
2863            })
2864        });
2865    }
2866
2867    #[test]
2868    fn builtin_force_reduce_list_builtins() {
2869        let program: Program<Name> = Program {
2870            version: (1, 0, 0),
2871            term: Term::mk_cons()
2872                .apply(Term::var("x"))
2873                .apply(Term::tail_list().apply(Term::head_list().apply(Term::var("y"))))
2874                .lambda("x")
2875                .lambda("y"),
2876        };
2877
2878        let expected = Program {
2879            version: (1, 0, 0),
2880            term: Term::var("__cons_list_wrapped")
2881                .apply(Term::var("x"))
2882                .apply(
2883                    Term::var("__tail_list_wrapped")
2884                        .apply(Term::var("__head_list_wrapped").apply(Term::var("y"))),
2885                )
2886                .lambda("x")
2887                .lambda("y")
2888                // Forces are automatically applied by builder
2889                .lambda("__cons_list_wrapped")
2890                .lambda("__head_list_wrapped")
2891                .lambda("__tail_list_wrapped")
2892                .apply(Term::tail_list())
2893                .apply(Term::head_list())
2894                .apply(Term::mk_cons()),
2895        };
2896
2897        compare_optimization(expected, program, |p| p.run_once_pass());
2898    }
2899
2900    #[test]
2901    fn builtin_force_reduce_if_builtin() {
2902        let program: Program<Name> = Program {
2903            version: (1, 0, 0),
2904            term: Term::equals_integer()
2905                .apply(Term::var("x"))
2906                .apply(
2907                    Term::add_integer()
2908                        .apply(Term::integer(2.into()))
2909                        .apply(Term::var("y")),
2910                )
2911                .delayed_if_then_else(
2912                    Term::length_of_bytearray().apply(Term::byte_string(vec![])),
2913                    Term::Error,
2914                )
2915                .lambda("x")
2916                .lambda("y"),
2917        };
2918
2919        let expected = Program {
2920            version: (1, 0, 0),
2921            term: Term::var("__if_then_else_wrapped")
2922                .apply(
2923                    Term::equals_integer().apply(Term::var("x")).apply(
2924                        Term::add_integer()
2925                            .apply(Term::integer(2.into()))
2926                            .apply(Term::var("y")),
2927                    ),
2928                )
2929                .apply(
2930                    Term::length_of_bytearray()
2931                        .apply(Term::byte_string(vec![]))
2932                        .delay(),
2933                )
2934                .apply(Term::Error.delay())
2935                .force()
2936                .lambda("x")
2937                .lambda("y")
2938                .lambda("__if_then_else_wrapped")
2939                .apply(Term::Builtin(DefaultFunction::IfThenElse).force()),
2940        };
2941
2942        compare_optimization(expected, program, |p| p.run_once_pass());
2943    }
2944
2945    #[test]
2946    fn builtin_force_reduce_pair_builtins() {
2947        let program: Program<Name> = Program {
2948            version: (1, 0, 0),
2949            term: Term::add_integer()
2950                .apply(Term::un_i_data().apply(Term::fst_pair().apply(Term::var("__pair"))))
2951                .apply(Term::un_i_data().apply(Term::snd_pair().apply(Term::var("__pair"))))
2952                .lambda("__pair")
2953                .apply(
2954                    Term::mk_pair_data()
2955                        .apply(Term::data(Data::integer(1.into())))
2956                        .apply(Term::data(Data::integer(5.into()))),
2957                ),
2958        };
2959
2960        let expected = Program {
2961            version: (1, 0, 0),
2962            term: Term::add_integer()
2963                .apply(
2964                    Term::un_i_data()
2965                        .apply(Term::var("__fst_pair_wrapped").apply(Term::var("__pair"))),
2966                )
2967                .apply(
2968                    Term::un_i_data()
2969                        .apply(Term::var("__snd_pair_wrapped").apply(Term::var("__pair"))),
2970                )
2971                .lambda("__pair")
2972                .apply(
2973                    Term::mk_pair_data()
2974                        .apply(Term::data(Data::integer(1.into())))
2975                        .apply(Term::data(Data::integer(5.into()))),
2976                )
2977                .lambda("__fst_pair_wrapped")
2978                .lambda("__snd_pair_wrapped")
2979                .apply(Term::snd_pair())
2980                .apply(Term::fst_pair()),
2981        };
2982
2983        compare_optimization(expected, program, |p| p.run_once_pass());
2984    }
2985
2986    #[test]
2987    fn identity_reduce_usage() {
2988        let program: Program<Name> = Program {
2989            version: (1, 0, 0),
2990            term: Term::sha2_256()
2991                .apply(Term::var("identity").apply(Term::var("x")))
2992                .lambda("x")
2993                .apply(Term::byte_string(vec![]).delay())
2994                .lambda("identity")
2995                .apply(Term::var("y").lambda("y")),
2996        };
2997
2998        let expected = Program {
2999            version: (1, 0, 0),
3000            term: Term::sha2_256()
3001                .apply(Term::var("x"))
3002                .lambda("x")
3003                .apply(Term::byte_string(vec![]).delay()),
3004        };
3005
3006        compare_optimization(expected, program, |p| {
3007            p.run_one_opt(true, &mut |id, term, arg_stack, scope, context| {
3008                term.identity_reducer(id, arg_stack, scope, context);
3009            })
3010        });
3011    }
3012
3013    #[test]
3014    fn identity_reduce_0_occurrence() {
3015        let program: Program<Name> = Program {
3016            version: (1, 0, 0),
3017            term: Term::sha2_256()
3018                .apply(Term::var("x"))
3019                .lambda("x")
3020                .apply(Term::byte_string(vec![]).delay())
3021                .lambda("identity")
3022                .apply(Term::var("y").lambda("y")),
3023        };
3024
3025        let expected = Program {
3026            version: (1, 0, 0),
3027            term: Term::sha2_256()
3028                .apply(Term::var("x"))
3029                .lambda("x")
3030                .apply(Term::byte_string(vec![]).delay()),
3031        };
3032
3033        compare_optimization(expected, program, |p| {
3034            p.run_one_opt(true, &mut |id, term, arg_stack, scope, context| {
3035                term.identity_reducer(id, arg_stack, scope, context);
3036            })
3037        });
3038    }
3039
3040    #[test]
3041    fn identity_reduce_param() {
3042        let program: Program<Name> = Program {
3043            version: (1, 0, 0),
3044            term: Term::sha2_256()
3045                .apply(
3046                    Term::var("f")
3047                        .apply(Term::var("x"))
3048                        .apply(Term::var("identity")),
3049                )
3050                .lambda("x")
3051                .apply(Term::byte_string(vec![]).delay())
3052                .lambda("identity")
3053                .apply(Term::var("y").lambda("y"))
3054                .lambda("f")
3055                .apply(
3056                    Term::var("with")
3057                        .apply(Term::var("x"))
3058                        .lambda("with")
3059                        .lambda("x"),
3060                ),
3061        };
3062
3063        let expected = Program {
3064            version: (1, 0, 0),
3065            term: Term::sha2_256()
3066                .apply(
3067                    Term::var("f")
3068                        .apply(Term::var("x"))
3069                        .apply(Term::var("identity")),
3070                )
3071                .lambda("x")
3072                .apply(Term::byte_string(vec![]).delay())
3073                .lambda("identity")
3074                .apply(Term::var("y").lambda("y"))
3075                .lambda("f")
3076                .apply(
3077                    Term::var("with")
3078                        .apply(Term::var("x"))
3079                        .lambda("with")
3080                        .lambda("x"),
3081                ),
3082        };
3083
3084        compare_optimization(expected, program, |p| {
3085            p.run_one_opt(true, &mut |id, term, arg_stack, scope, context| {
3086                term.identity_reducer(id, arg_stack, scope, context);
3087            })
3088        });
3089    }
3090
3091    #[test]
3092    fn identity_reduce_no_inline() {
3093        let program: Program<Name> = Program {
3094            version: (1, 0, 0),
3095            term: Term::sha2_256()
3096                .apply(Term::var("identity").apply(Term::var("x")))
3097                .lambda("x")
3098                .apply(Term::byte_string(vec![]).delay())
3099                .lambda("identity")
3100                .apply(Term::var("y").lambda("y").lambda(NO_INLINE)),
3101        };
3102
3103        let expected = Program {
3104            version: (1, 0, 0),
3105            term: Term::sha2_256()
3106                .apply(Term::var("x"))
3107                .lambda("x")
3108                .apply(Term::byte_string(vec![]).delay()),
3109        };
3110
3111        compare_optimization(expected, program, |p| {
3112            p.run_one_opt(true, &mut |id, term, arg_stack, scope, context| {
3113                term.identity_reducer(id, arg_stack, scope, context);
3114            })
3115        });
3116    }
3117
3118    #[test]
3119    fn identity_reduce_no_inline_2() {
3120        let program: Program<Name> = Program {
3121            version: (1, 0, 0),
3122            term: Term::sha2_256()
3123                .apply(Term::var("identity").apply(Term::var("x")))
3124                .lambda("x")
3125                .apply(Term::byte_string(vec![]).delay())
3126                .lambda(NO_INLINE)
3127                .lambda("identity")
3128                .apply(Term::var("y").lambda("y").lambda(NO_INLINE)),
3129        };
3130
3131        let expected = Program {
3132            version: (1, 0, 0),
3133            term: Term::sha2_256()
3134                .apply(Term::var("x"))
3135                .lambda("x")
3136                .apply(Term::byte_string(vec![]).delay())
3137                .lambda(NO_INLINE),
3138        };
3139
3140        compare_optimization(expected, program, |p| {
3141            p.run_one_opt(true, &mut |id, term, arg_stack, scope, context| {
3142                term.identity_reducer(id, arg_stack, scope, context);
3143            })
3144        });
3145    }
3146
3147    #[test]
3148    fn inline_reduce_delay_sha() {
3149        let program: Program<Name> = Program {
3150            version: (1, 0, 0),
3151            term: Term::sha2_256()
3152                .apply(Term::var("x"))
3153                .lambda("x")
3154                .apply(Term::byte_string(vec![]).delay()),
3155        };
3156
3157        let expected = Program {
3158            version: (1, 0, 0),
3159            term: Term::sha2_256().apply(Term::byte_string(vec![]).delay()),
3160        };
3161
3162        compare_optimization(expected, program, |p| {
3163            p.run_one_opt(true, &mut |id, term, arg_stack, scope, context| {
3164                term.inline_reducer(id, arg_stack, scope, context);
3165            })
3166        });
3167    }
3168
3169    #[test]
3170    fn inline_reduce_if_then_else_then() {
3171        let program: Program<Name> = Program {
3172            version: (1, 0, 0),
3173            term: Term::var("__if_then_else_wrapped")
3174                .apply(Term::bool(true))
3175                .apply(Term::sha3_256().apply(Term::var("x")).delay())
3176                .apply(Term::Error.delay())
3177                .force()
3178                .lambda("x")
3179                .apply(Term::sha3_256().apply(Term::byte_string(vec![])))
3180                .lambda("__if_then_else_wrapped")
3181                .apply(Term::Builtin(DefaultFunction::IfThenElse).force()),
3182        };
3183
3184        let expected = Program {
3185            version: (1, 0, 0),
3186            term: Term::var("__if_then_else_wrapped")
3187                .apply(Term::bool(true))
3188                .apply(
3189                    Term::sha3_256()
3190                        .apply(Term::sha3_256().apply(Term::byte_string(vec![])))
3191                        .delay(),
3192                )
3193                .apply(Term::Error.delay())
3194                .force()
3195                .lambda("__if_then_else_wrapped")
3196                .apply(Term::Builtin(DefaultFunction::IfThenElse).force()),
3197        };
3198
3199        compare_optimization(expected, program, |p| {
3200            p.run_one_opt(true, &mut |id, term, arg_stack, scope, context| {
3201                term.inline_reducer(id, arg_stack, scope, context);
3202            })
3203        });
3204    }
3205
3206    #[test]
3207    fn inline_reduce_if_then_else_else() {
3208        let program: Program<Name> = Program {
3209            version: (1, 0, 0),
3210            term: Term::var("__if_then_else_wrapped")
3211                .apply(Term::bool(true))
3212                .apply(Term::Error.delay())
3213                .apply(Term::sha3_256().apply(Term::var("x")).delay())
3214                .force()
3215                .lambda("x")
3216                .apply(Term::sha3_256().apply(Term::byte_string(vec![])))
3217                .lambda("__if_then_else_wrapped")
3218                .apply(Term::Builtin(DefaultFunction::IfThenElse).force()),
3219        };
3220
3221        let expected = Program {
3222            version: (1, 0, 0),
3223            term: Term::var("__if_then_else_wrapped")
3224                .apply(Term::bool(true))
3225                .apply(Term::Error.delay())
3226                .apply(
3227                    Term::sha3_256()
3228                        .apply(Term::sha3_256().apply(Term::byte_string(vec![])))
3229                        .delay(),
3230                )
3231                .force()
3232                .lambda("__if_then_else_wrapped")
3233                .apply(Term::Builtin(DefaultFunction::IfThenElse).force()),
3234        };
3235
3236        compare_optimization(expected, program, |p| {
3237            p.run_one_opt(true, &mut |id, term, arg_stack, scope, context| {
3238                term.inline_reducer(id, arg_stack, scope, context);
3239            })
3240        });
3241    }
3242
3243    #[test]
3244    fn inline_reduce_0_occurrence() {
3245        let program: Program<Name> = Program {
3246            version: (1, 0, 0),
3247            term: Term::sha2_256()
3248                .lambda("x")
3249                .apply(Term::byte_string(vec![]).delay()),
3250        };
3251
3252        let expected = Program {
3253            version: (1, 0, 0),
3254            term: Term::sha2_256(),
3255        };
3256
3257        compare_optimization(expected, program, |p| {
3258            p.run_one_opt(true, &mut |id, term, arg_stack, scope, context| {
3259                term.inline_reducer(id, arg_stack, scope, context);
3260            })
3261        });
3262    }
3263
3264    #[test]
3265    fn wrap_data_reduce_i_data() {
3266        let program: Program<Name> = Program {
3267            version: (1, 0, 0),
3268            term: Term::equals_data()
3269                .apply(Term::i_data().apply(Term::un_i_data().apply(Term::Constant(
3270                    Constant::Data(PlutusData::BigInt(BigInt::Int(5.into()))).into(),
3271                ))))
3272                .apply(Term::i_data().apply(Term::integer(1.into())))
3273                .lambda("x"),
3274        };
3275
3276        let expected = Program {
3277            version: (1, 0, 0),
3278            term: Term::equals_data()
3279                .apply(Term::Constant(
3280                    Constant::Data(PlutusData::BigInt(BigInt::Int(5.into()))).into(),
3281                ))
3282                .apply(Term::data(Data::integer(1.into())))
3283                .lambda("x"),
3284        };
3285
3286        compare_optimization(expected, program, |p| {
3287            p.run_one_opt(true, &mut |id, term, arg_stack, scope, context| {
3288                term.cast_data_reducer(id, arg_stack, scope, context);
3289            })
3290        });
3291    }
3292
3293    #[test]
3294    fn wrap_data_reduce_un_i_data() {
3295        let program: Program<Name> = Program {
3296            version: (1, 0, 0),
3297            term: Term::equals_integer()
3298                .apply(Term::un_i_data().apply(Term::i_data().apply(Term::integer(1.into()))))
3299                .apply(Term::un_i_data().apply(Term::Constant(
3300                    Constant::Data(PlutusData::BigInt(BigInt::Int(5.into()))).into(),
3301                )))
3302                .lambda("x"),
3303        };
3304
3305        let expected = Program {
3306            version: (1, 0, 0),
3307            term: Term::equals_integer()
3308                .apply(Term::integer(1.into()))
3309                .apply(Term::integer(5.into()))
3310                .lambda("x"),
3311        };
3312
3313        compare_optimization(expected, program, |p| {
3314            p.run_one_opt(true, &mut |id, term, arg_stack, scope, context| {
3315                term.cast_data_reducer(id, arg_stack, scope, context);
3316            })
3317        });
3318    }
3319
3320    #[test]
3321    fn curry_reducer_test_1() {
3322        let program: Program<Name> = Program {
3323            version: (1, 0, 0),
3324            term: Term::add_integer()
3325                .apply(Term::var("x"))
3326                .apply(Term::integer(1.into()))
3327                .lambda("x")
3328                .apply(
3329                    Term::add_integer()
3330                        .apply(Term::integer(1.into()))
3331                        .apply(Term::var("y")),
3332                )
3333                .lambda("y")
3334                .apply(
3335                    Term::add_integer()
3336                        .apply(Term::var("g"))
3337                        .apply(Term::integer(1.into())),
3338                )
3339                .lambda("g"),
3340        };
3341
3342        let expected = Program {
3343            version: (1, 0, 0),
3344            term: Term::var("add_one_curried")
3345                .apply(Term::var("x"))
3346                .lambda("x")
3347                .apply(Term::var("add_one_curried").apply(Term::var("y")))
3348                .lambda("y")
3349                .apply(Term::var("add_one_curried").apply(Term::var("g")))
3350                .lambda("add_one_curried")
3351                .apply(Term::add_integer().apply(Term::integer(1.into())))
3352                .lambda("g"),
3353        };
3354
3355        compare_optimization(expected, program, |p| p.builtin_curry_reducer());
3356    }
3357
3358    #[test]
3359    fn curry_reducer_test_2() {
3360        let program: Program<Name> = Program {
3361            version: (1, 0, 0),
3362            term: Term::add_integer()
3363                .apply(Term::var("x"))
3364                .apply(Term::integer(1.into()))
3365                .lambda("x")
3366                .apply(
3367                    Term::add_integer()
3368                        .apply(Term::integer(1.into()))
3369                        .apply(Term::var("y")),
3370                )
3371                .lambda("y")
3372                .apply(Term::integer(5.into())),
3373        };
3374
3375        let expected = Program {
3376            version: (1, 0, 0),
3377            term: Term::add_integer()
3378                .apply(Term::var("x"))
3379                .apply(Term::integer(1.into()))
3380                .lambda("x")
3381                .apply(
3382                    Term::add_integer()
3383                        .apply(Term::integer(1.into()))
3384                        .apply(Term::var("y")),
3385                )
3386                .lambda("y")
3387                .apply(Term::integer(5.into())),
3388        };
3389
3390        compare_optimization(expected, program, |p| p.builtin_curry_reducer());
3391    }
3392
3393    #[test]
3394    fn curry_reducer_test_3() {
3395        let program: Program<Name> = Program {
3396            version: (1, 0, 0),
3397            term: Term::var("equivalence")
3398                .lambda("equivalence")
3399                .apply(
3400                    Term::equals_integer()
3401                        .apply(Term::integer(0.into()))
3402                        .apply(Term::var(CONSTR_INDEX_EXPOSER).apply(Term::var("tuple_index_0")))
3403                        .if_then_else(
3404                            Term::equals_integer()
3405                                .apply(Term::integer(0.into()))
3406                                .apply(
3407                                    Term::var(CONSTR_INDEX_EXPOSER)
3408                                        .apply(Term::var("tuple_index_1")),
3409                                )
3410                                .if_then_else(
3411                                    Term::equals_integer()
3412                                        .apply(
3413                                            Term::subtract_integer()
3414                                                .apply(Term::var("x2"))
3415                                                .apply(Term::var("x1")),
3416                                        )
3417                                        .apply(Term::integer(0.into()))
3418                                        .delayed_if_then_else(
3419                                            Term::equals_integer()
3420                                                .apply(
3421                                                    Term::subtract_integer()
3422                                                        .apply(Term::var("y2"))
3423                                                        .apply(Term::var("y1")),
3424                                                )
3425                                                .apply(Term::integer(0.into())),
3426                                            Term::bool(false),
3427                                        )
3428                                        .lambda("x2")
3429                                        .apply(Term::un_i_data().apply(
3430                                            Term::fst_pair().apply(Term::var("field_0_pair")),
3431                                        ))
3432                                        .lambda("y2")
3433                                        .apply(Term::un_i_data().apply(
3434                                            Term::snd_pair().apply(Term::var("field_0_pair")),
3435                                        ))
3436                                        .lambda("field_0_pair")
3437                                        .apply(
3438                                            Term::mk_pair_data()
3439                                                .apply(
3440                                                    Term::head_list()
3441                                                        .apply(Term::var("__list_data")),
3442                                                )
3443                                                .apply(Term::head_list().apply(Term::var("__tail")))
3444                                                .lambda("__tail")
3445                                                .apply(
3446                                                    Term::tail_list()
3447                                                        .apply(Term::var("__list_data")),
3448                                                )
3449                                                .lambda("__list_data")
3450                                                .apply(
3451                                                    Term::unlist_data().apply(
3452                                                        Term::head_list().apply(Term::var(
3453                                                            "tuple_index_1_fields",
3454                                                        )),
3455                                                    ),
3456                                                ),
3457                                        )
3458                                        .lambda("tuple_index_1_fields")
3459                                        .apply(
3460                                            Term::var(CONSTR_FIELDS_EXPOSER)
3461                                                .apply(Term::var("tuple_index_1")),
3462                                        )
3463                                        .delay(),
3464                                    Term::var("clauses_delayed"),
3465                                )
3466                                .force()
3467                                .lambda("x1")
3468                                .apply(
3469                                    Term::un_i_data()
3470                                        .apply(Term::fst_pair().apply(Term::var("field_0_pair"))),
3471                                )
3472                                .lambda("y1")
3473                                .apply(
3474                                    Term::un_i_data()
3475                                        .apply(Term::snd_pair().apply(Term::var("field_0_pair"))),
3476                                )
3477                                .lambda("field_0_pair")
3478                                .apply(
3479                                    Term::mk_pair_data()
3480                                        .apply(Term::head_list().apply(Term::var("__list_data")))
3481                                        .apply(Term::head_list().apply(Term::var("__tail")))
3482                                        .lambda("__tail")
3483                                        .apply(Term::tail_list().apply(Term::var("__list_data")))
3484                                        .lambda("__list_data")
3485                                        .apply(
3486                                            Term::unlist_data().apply(
3487                                                Term::head_list()
3488                                                    .apply(Term::var("tuple_index_0_fields")),
3489                                            ),
3490                                        ),
3491                                )
3492                                .lambda("tuple_index_0_fields")
3493                                .apply(
3494                                    Term::var(CONSTR_FIELDS_EXPOSER)
3495                                        .apply(Term::var("tuple_index_0")),
3496                                )
3497                                .delay(),
3498                            Term::var("clauses_delayed"),
3499                        )
3500                        .force()
3501                        .lambda("clauses_delayed")
3502                        .apply(
3503                            Term::equals_integer()
3504                                .apply(Term::integer(1.into()))
3505                                .apply(
3506                                    Term::var(CONSTR_INDEX_EXPOSER)
3507                                        .apply(Term::var("tuple_index_0")),
3508                                )
3509                                .if_then_else(
3510                                    Term::equals_integer()
3511                                        .apply(Term::integer(1.into()))
3512                                        .apply(
3513                                            Term::var(CONSTR_INDEX_EXPOSER)
3514                                                .apply(Term::var("tuple_index_1")),
3515                                        )
3516                                        .if_then_else(
3517                                            Term::bool(true).delay(),
3518                                            Term::var("clauses_delayed"),
3519                                        )
3520                                        .force()
3521                                        .delay(),
3522                                    Term::var("clauses_delayed"),
3523                                )
3524                                .force()
3525                                .lambda("clauses_delayed")
3526                                .apply(
3527                                    Term::equals_integer()
3528                                        .apply(Term::integer(1.into()))
3529                                        .apply(
3530                                            Term::var(CONSTR_INDEX_EXPOSER)
3531                                                .apply(Term::var("tuple_index_0")),
3532                                        )
3533                                        .if_then_else(
3534                                            Term::equals_integer()
3535                                                .apply(Term::integer(0.into()))
3536                                                .apply(
3537                                                    Term::var(CONSTR_INDEX_EXPOSER)
3538                                                        .apply(Term::var("tuple_index_1")),
3539                                                )
3540                                                .if_then_else(
3541                                                    Term::bool(false).delay(),
3542                                                    Term::var("clauses_delayed"),
3543                                                )
3544                                                .force()
3545                                                .delay(),
3546                                            Term::var("clauses_delayed"),
3547                                        )
3548                                        .force()
3549                                        .lambda("clauses_delayed")
3550                                        .apply(Term::bool(false).delay())
3551                                        .delay(),
3552                                )
3553                                .delay(),
3554                        )
3555                        .lambda("tuple_index_0")
3556                        .apply(Term::fst_pair().apply(Term::var("input")))
3557                        .lambda("tuple_index_1")
3558                        .apply(Term::snd_pair().apply(Term::var("input")))
3559                        .lambda("input")
3560                        .apply(
3561                            Term::mk_pair_data()
3562                                .apply(Term::var("ec1"))
3563                                .apply(Term::var("ec2")),
3564                        )
3565                        .lambda("ec2")
3566                        .lambda("ec1"),
3567                )
3568                .apply(Term::data(Data::constr(1, vec![])))
3569                .apply(Term::data(Data::constr(1, vec![])))
3570                .delayed_if_then_else(
3571                    Term::bool(true),
3572                    Term::bool(true).if_then_else(Term::bool(false), Term::bool(true)),
3573                )
3574                .constr_index_exposer()
3575                .constr_fields_exposer(),
3576        };
3577
3578        let expected = Program {
3579            version: (1, 0, 0),
3580            term: Term::var("equivalence")
3581                .lambda("equivalence")
3582                .apply(
3583                    Term::var("equals_integer_0_curried")
3584                        .apply(Term::var(CONSTR_INDEX_EXPOSER).apply(Term::var("tuple_index_0")))
3585                        .if_then_else(
3586                            Term::var("equals_integer_0_curried")
3587                                .apply(
3588                                    Term::var(CONSTR_INDEX_EXPOSER)
3589                                        .apply(Term::var("tuple_index_1")),
3590                                )
3591                                .if_then_else(
3592                                    Term::var("equals_integer_0_curried")
3593                                        .apply(
3594                                            Term::subtract_integer()
3595                                                .apply(Term::var("x2"))
3596                                                .apply(Term::var("x1")),
3597                                        )
3598                                        .delayed_if_then_else(
3599                                            Term::var("equals_integer_0_curried").apply(
3600                                                Term::subtract_integer()
3601                                                    .apply(Term::var("y2"))
3602                                                    .apply(Term::var("y1")),
3603                                            ),
3604                                            Term::bool(false),
3605                                        )
3606                                        .lambda("x2")
3607                                        .apply(Term::un_i_data().apply(
3608                                            Term::fst_pair().apply(Term::var("field_0_pair")),
3609                                        ))
3610                                        .lambda("y2")
3611                                        .apply(Term::un_i_data().apply(
3612                                            Term::snd_pair().apply(Term::var("field_0_pair")),
3613                                        ))
3614                                        .lambda("field_0_pair")
3615                                        .apply(
3616                                            Term::mk_pair_data()
3617                                                .apply(
3618                                                    Term::head_list()
3619                                                        .apply(Term::var("__list_data")),
3620                                                )
3621                                                .apply(Term::head_list().apply(Term::var("__tail")))
3622                                                .lambda("__tail")
3623                                                .apply(
3624                                                    Term::tail_list()
3625                                                        .apply(Term::var("__list_data")),
3626                                                )
3627                                                .lambda("__list_data")
3628                                                .apply(
3629                                                    Term::unlist_data().apply(
3630                                                        Term::head_list().apply(Term::var(
3631                                                            "tuple_index_1_fields",
3632                                                        )),
3633                                                    ),
3634                                                ),
3635                                        )
3636                                        .lambda("tuple_index_1_fields")
3637                                        .apply(
3638                                            Term::var(CONSTR_FIELDS_EXPOSER)
3639                                                .apply(Term::var("tuple_index_1")),
3640                                        )
3641                                        .delay(),
3642                                    Term::var("clauses_delayed"),
3643                                )
3644                                .force()
3645                                .lambda("x1")
3646                                .apply(
3647                                    Term::un_i_data()
3648                                        .apply(Term::fst_pair().apply(Term::var("field_0_pair"))),
3649                                )
3650                                .lambda("y1")
3651                                .apply(
3652                                    Term::un_i_data()
3653                                        .apply(Term::snd_pair().apply(Term::var("field_0_pair"))),
3654                                )
3655                                .lambda("field_0_pair")
3656                                .apply(
3657                                    Term::mk_pair_data()
3658                                        .apply(Term::head_list().apply(Term::var("__list_data")))
3659                                        .apply(Term::head_list().apply(Term::var("__tail")))
3660                                        .lambda("__tail")
3661                                        .apply(Term::tail_list().apply(Term::var("__list_data")))
3662                                        .lambda("__list_data")
3663                                        .apply(
3664                                            Term::unlist_data().apply(
3665                                                Term::head_list()
3666                                                    .apply(Term::var("tuple_index_0_fields")),
3667                                            ),
3668                                        ),
3669                                )
3670                                .lambda("tuple_index_0_fields")
3671                                .apply(
3672                                    Term::var(CONSTR_FIELDS_EXPOSER)
3673                                        .apply(Term::var("tuple_index_0")),
3674                                )
3675                                .delay(),
3676                            Term::var("clauses_delayed"),
3677                        )
3678                        .force()
3679                        .lambda("clauses_delayed")
3680                        .apply(
3681                            Term::var("equals_integer_1_curried")
3682                                .apply(
3683                                    Term::var(CONSTR_INDEX_EXPOSER)
3684                                        .apply(Term::var("tuple_index_0")),
3685                                )
3686                                .if_then_else(
3687                                    Term::var("equals_integer_1_curried")
3688                                        .apply(
3689                                            Term::var(CONSTR_INDEX_EXPOSER)
3690                                                .apply(Term::var("tuple_index_1")),
3691                                        )
3692                                        .if_then_else(
3693                                            Term::bool(true).delay(),
3694                                            Term::var("clauses_delayed"),
3695                                        )
3696                                        .force()
3697                                        .delay(),
3698                                    Term::var("clauses_delayed"),
3699                                )
3700                                .force()
3701                                .lambda("clauses_delayed")
3702                                .apply(
3703                                    Term::var("equals_integer_1_curried")
3704                                        .apply(
3705                                            Term::var(CONSTR_INDEX_EXPOSER)
3706                                                .apply(Term::var("tuple_index_0")),
3707                                        )
3708                                        .if_then_else(
3709                                            Term::var("equals_integer_0_curried")
3710                                                .apply(
3711                                                    Term::var(CONSTR_INDEX_EXPOSER)
3712                                                        .apply(Term::var("tuple_index_1")),
3713                                                )
3714                                                .if_then_else(
3715                                                    Term::bool(false).delay(),
3716                                                    Term::var("clauses_delayed"),
3717                                                )
3718                                                .force()
3719                                                .delay(),
3720                                            Term::var("clauses_delayed"),
3721                                        )
3722                                        .force()
3723                                        .lambda("clauses_delayed")
3724                                        .apply(Term::bool(false).delay())
3725                                        .delay(),
3726                                )
3727                                .lambda("equals_integer_1_curried")
3728                                .apply(Term::equals_integer().apply(Term::integer(1.into())))
3729                                .delay(),
3730                        )
3731                        .lambda("equals_integer_0_curried")
3732                        .apply(Term::equals_integer().apply(Term::integer(0.into())))
3733                        .lambda("tuple_index_0")
3734                        .apply(Term::fst_pair().apply(Term::var("input")))
3735                        .lambda("tuple_index_1")
3736                        .apply(Term::snd_pair().apply(Term::var("input")))
3737                        .lambda("input")
3738                        .apply(
3739                            Term::mk_pair_data()
3740                                .apply(Term::var("ec1"))
3741                                .apply(Term::var("ec2")),
3742                        )
3743                        .lambda("ec2")
3744                        .lambda("ec1"),
3745                )
3746                .apply(Term::data(Data::constr(1, vec![])))
3747                .apply(Term::data(Data::constr(1, vec![])))
3748                .delayed_if_then_else(
3749                    Term::bool(true),
3750                    Term::bool(true).if_then_else(Term::bool(false), Term::bool(true)),
3751                )
3752                .constr_index_exposer()
3753                .constr_fields_exposer(),
3754        };
3755
3756        compare_optimization(expected, program, |p| p.builtin_curry_reducer());
3757    }
3758
3759    #[test]
3760    fn curry_reducer_test_4() {
3761        let program: Program<Name> = Program {
3762            version: (1, 0, 0),
3763            term: Term::bool(true)
3764                .delayed_if_then_else(
3765                    Term::integer(2.into()),
3766                    Term::add_integer()
3767                        .apply(
3768                            Term::add_integer()
3769                                .apply(Term::var("x"))
3770                                .apply(Term::var("y")),
3771                        )
3772                        .apply(Term::var("z"))
3773                        .lambda("z")
3774                        .apply(
3775                            Term::index_bytearray()
3776                                .apply(Term::byte_string(vec![
3777                                    1, 2, 4, 8, 16, 32, 64, 128, 255, 255,
3778                                ]))
3779                                .apply(Term::integer(35.into())),
3780                        )
3781                        .lambda("y")
3782                        .apply(
3783                            Term::index_bytearray()
3784                                .apply(Term::byte_string(vec![
3785                                    1, 2, 4, 8, 16, 32, 64, 128, 255, 255,
3786                                ]))
3787                                .apply(Term::integer(35.into())),
3788                        ),
3789                )
3790                .lambda("x")
3791                .apply(
3792                    Term::bool(true).delayed_if_then_else(
3793                        Term::integer(1.into()),
3794                        Term::index_bytearray()
3795                            .apply(Term::byte_string(vec![
3796                                1, 2, 4, 8, 16, 32, 64, 128, 255, 255,
3797                            ]))
3798                            .apply(Term::integer(35.into())),
3799                    ),
3800                ),
3801        };
3802
3803        let expected = Program {
3804            version: (1, 0, 0),
3805            term: Term::bool(true)
3806                .delayed_if_then_else(
3807                    Term::integer(2.into()),
3808                    Term::add_integer()
3809                        .apply(
3810                            Term::add_integer()
3811                                .apply(Term::var("x"))
3812                                .apply(Term::var("y")),
3813                        )
3814                        .apply(Term::var("z"))
3815                        .lambda("z")
3816                        .apply(Term::var("good_curry").apply(Term::integer(35.into())))
3817                        .lambda("y")
3818                        .apply(Term::var("good_curry").apply(Term::integer(35.into()))),
3819                )
3820                .lambda("x")
3821                .apply(Term::bool(true).delayed_if_then_else(
3822                    Term::integer(1.into()),
3823                    Term::var("good_curry").apply(Term::integer(35.into())),
3824                ))
3825                .lambda("good_curry")
3826                .apply(Term::index_bytearray().apply(Term::byte_string(vec![
3827                    1, 2, 4, 8, 16, 32, 64, 128, 255, 255,
3828                ]))),
3829        };
3830
3831        compare_optimization(expected, program, |p| p.builtin_curry_reducer());
3832    }
3833
3834    #[test]
3835    fn case_constr_apply_test_1() {
3836        let program: Program<Name> = Program {
3837            version: (1, 1, 0),
3838            term: Term::add_integer()
3839                .apply(Term::integer(0.into()))
3840                .apply(Term::integer(0.into()))
3841                .apply(Term::integer(0.into()))
3842                .apply(Term::integer(0.into()))
3843                .apply(Term::integer(0.into()))
3844                .apply(Term::integer(0.into())),
3845        };
3846
3847        let expected = Program {
3848            version: (1, 1, 0),
3849            term: Term::constr(
3850                0,
3851                vec![
3852                    Term::integer(0.into()),
3853                    Term::integer(0.into()),
3854                    Term::integer(0.into()),
3855                    Term::integer(0.into()),
3856                    Term::integer(0.into()),
3857                    Term::integer(0.into()),
3858                ],
3859            )
3860            .case(vec![Term::add_integer()]),
3861        };
3862
3863        compare_optimization(expected, program, |p| {
3864            p.run_one_opt(true, &mut |id, term, arg_stack, scope, context| {
3865                term.case_constr_apply_reducer(id, arg_stack, scope, context);
3866            })
3867        });
3868    }
3869
3870    #[test]
3871    fn case_constr_apply_test_2() {
3872        let program: Program<Name> = Program {
3873            version: (1, 1, 0),
3874            term: Term::add_integer()
3875                .apply(Term::integer(0.into()))
3876                .apply(Term::integer(0.into())),
3877        };
3878
3879        let expected = Program {
3880            version: (1, 1, 0),
3881            term: Term::add_integer()
3882                .apply(Term::integer(0.into()))
3883                .apply(Term::integer(0.into())),
3884        };
3885
3886        compare_optimization(expected, program, |p| {
3887            p.run_one_opt(true, &mut |id, term, arg_stack, scope, context| {
3888                term.case_constr_apply_reducer(id, arg_stack, scope, context);
3889            })
3890        });
3891    }
3892}