Merge pull request #245 from HigherOrderCO/bug/sc-525/float-combinators-not-extracting-some-terms

[sc-525] Float combinators not extracting some terms
This commit is contained in:
Nicolas Abril 2024-03-29 08:30:01 +00:00 committed by GitHub
commit 817aec9d4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 267 additions and 313 deletions

View File

@ -45,6 +45,7 @@
"namegen",
"nams",
"nats",
"nullary",
"numop",
"nums",
"oper",
@ -54,6 +55,7 @@
"peekable",
"postcondition",
"readback",
"recursively",
"redex",
"redexes",
"resugar",

View File

@ -231,6 +231,7 @@ pub struct Name(GlobalString);
/// A macro for creating iterators that can have statically known
/// different types. Useful for iterating over tree children, where
/// each tree node variant yields a different iterator type.
#[macro_export]
macro_rules! multi_iterator {
($Iter:ident { $($Variant:ident),* $(,)? }) => {
#[derive(Debug, Clone)]

View File

@ -1,277 +1,175 @@
use crate::term::{Book, Definition, Name, Rule, Term};
use std::{
collections::{BTreeMap, BTreeSet},
ops::BitAnd,
};
use indexmap::IndexSet;
use crate::{
multi_iterator,
term::{Book, Definition, Name, Rule, Term},
};
use std::collections::BTreeMap;
type Combinators = BTreeMap<Name, Definition>;
/// Replaces closed Terms (i.e. without free variables) with a Ref to the extracted term
/// Precondition: Vars must have been sanitized
impl Book {
/// Extracts unsafe terms into new definitions.
///
/// Precondition: Variables must have been sanitized.
///
/// The floating algorithm follows these rules:
/// - Recursively float every child term.
/// - Extract if it is a combinator and is not a safe term.
/// See [`Term::is_safe`] for what is considered safe here.
pub fn float_combinators(&mut self) {
let mut combinators = Combinators::new();
let slf = self.clone();
for (def_name, def) in self.defs.iter_mut() {
let mut name_gen = 0;
if self.entrypoint.as_ref().is_some_and(|m| m == def_name) {
continue;
}
let builtin = def.builtin;
let rule = def.rule_mut();
rule.body.float_combinators(def_name, builtin, &mut combinators);
let mut seen = IndexSet::new();
rule.body.float_combinators(&mut combinators, &mut name_gen, &slf, def_name, builtin, &mut seen);
}
// Definitions are not inserted to the book as they are defined to appease the borrow checker.
// Since we are mut borrowing the rules we can't borrow the book to insert at the same time.
self.defs.extend(combinators);
}
}
type Combinators = BTreeMap<Name, Definition>;
impl Term {
fn float_combinators(
&mut self,
combinators: &mut Combinators,
name_gen: &mut usize,
book: &Book,
def_name: &Name,
builtin: bool,
seen: &mut IndexSet<Name>,
) {
for term in self.float_children_mut() {
// Recursively float the children terms.
term.float_combinators(combinators, name_gen, book, def_name, builtin, seen);
#[derive(Debug)]
struct TermInfo<'d> {
// Number of times a Term has been detached from the current Term
counter: u32,
def_name: Name,
builtin: bool,
needed_names: BTreeSet<Name>,
combinators: &'d mut Combinators,
}
impl<'d> TermInfo<'d> {
fn new(def_name: Name, builtin: bool, combinators: &'d mut Combinators) -> Self {
Self { counter: 0, def_name, builtin, needed_names: BTreeSet::new(), combinators }
}
fn request_name(&mut self, name: &Name) {
self.needed_names.insert(name.clone());
}
fn provide(&mut self, name: Option<&Name>) {
if let Some(name) = name {
self.needed_names.remove(name);
if term.is_combinator() && !term.is_safe(book, seen) {
float_combinator(def_name, name_gen, term, builtin, combinators);
}
}
}
fn has_no_free_vars(&self) -> bool {
self.needed_names.is_empty()
}
fn replace_scope(&mut self, new_scope: BTreeSet<Name>) -> BTreeSet<Name> {
std::mem::replace(&mut self.needed_names, new_scope)
}
fn merge_scope(&mut self, target: BTreeSet<Name>) {
self.needed_names.extend(target);
}
fn detach_term(&mut self, term: &mut Term) {
let comb_name = Name::new(format!("{}$C{}", self.def_name, self.counter));
self.counter += 1;
let comb_var = Term::Ref { nam: comb_name.clone() };
let extracted_term = std::mem::replace(term, comb_var);
let rules = vec![Rule { body: extracted_term, pats: Vec::new() }];
let rule = Definition { name: comb_name.clone(), rules, builtin: self.builtin };
self.combinators.insert(comb_name, rule);
}
}
#[derive(Debug)]
enum Detach {
/// Can be detached freely
Combinator,
/// Can not be detached
Unscoped { lams: BTreeSet<Name>, vars: BTreeSet<Name> },
/// Should be detached to make the program not hang
Recursive,
/// Inserts a new definition for the given term in the combinators map.
fn float_combinator(
def_name: &Name,
name_gen: &mut usize,
term: &mut Term,
builtin: bool,
combinators: &mut BTreeMap<Name, Definition>,
) {
let comb_name = Name::new(format!("{}$C{}", def_name, *name_gen));
*name_gen += 1;
let comb_ref = Term::Ref { nam: comb_name.clone() };
let extracted_term = std::mem::replace(term, comb_ref);
let rules = vec![Rule { body: extracted_term, pats: Vec::new() }];
let rule = Definition { name: comb_name.clone(), rules, builtin };
combinators.insert(comb_name, rule);
}
impl Detach {
fn can_detach(&self) -> bool {
!matches!(self, Detach::Unscoped { .. })
}
impl Term {
/// A term can be considered safe if it is:
/// - A Number or an Eraser.
/// - A Tuple or Superposition where all elements are safe.
/// - A safe Lambda, e.g. a nullary constructor or a lambda with safe body.
/// - A Reference with safe body.
pub fn is_safe(&self, book: &Book, seen: &mut IndexSet<Name>) -> bool {
Term::recursive_call(move || match self {
Term::Num { .. } | Term::Era => true,
fn unscoped_lam(nam: Name) -> Self {
Detach::Unscoped { lams: [nam].into(), vars: Default::default() }
}
Term::Tup { els } | Term::Sup { els, .. } => els.iter().all(|e| Term::is_safe(e, book, seen)),
fn unscoped_var(nam: Name) -> Self {
Detach::Unscoped { lams: Default::default(), vars: [nam].into() }
}
}
Term::Lam { .. } => self.is_safe_lambda(book, seen),
impl BitAnd for Detach {
type Output = Detach;
Term::Ref { nam } => {
if seen.contains(nam) {
return false;
}
fn bitand(self, rhs: Self) -> Self::Output {
match (self, rhs) {
(Detach::Combinator, Detach::Combinator) => Detach::Combinator,
(Detach::Combinator, unscoped @ Detach::Unscoped { .. }) => unscoped,
(unscoped @ Detach::Unscoped { .. }, Detach::Combinator) => unscoped,
// Merge two unscoped terms into one, removing entries of each matching `Lam` and `Var`.
// If all unscoped pairs are found and removed this way, the term can be treated as a Combinator.
(Detach::Unscoped { mut lams, mut vars }, Detach::Unscoped { lams: rhs_lams, vars: rhs_vars }) => {
lams.extend(rhs_lams);
vars.extend(rhs_vars);
let res_lams: BTreeSet<_> = lams.difference(&vars).cloned().collect();
let res_vars: BTreeSet<_> = vars.difference(&lams).cloned().collect();
if res_lams.is_empty() && res_vars.is_empty() {
Detach::Combinator
seen.insert(nam.clone());
if let Some(definition) = book.defs.get(nam) {
definition.rule().body.is_safe(book, seen)
} else {
Detach::Unscoped { lams: res_lams, vars: res_vars }
false
}
}
(Detach::Combinator, Detach::Recursive) => Detach::Recursive,
(Detach::Recursive, Detach::Combinator) => Detach::Recursive,
(Detach::Recursive, Detach::Recursive) => Detach::Recursive,
_ => false,
})
}
// TODO: Is this the best way to best deal with a term that is both unscoped and recursive?
(unscoped @ Detach::Unscoped { .. }, Detach::Recursive) => unscoped,
(Detach::Recursive, unscoped @ Detach::Unscoped { .. }) => unscoped,
/// Checks if the term is a lambda sequence with the body being a variable in the scope or a reference.
fn is_safe_lambda(&self, book: &Book, seen: &mut IndexSet<Name>) -> bool {
let mut current = self;
let mut scope = Vec::new();
while let Term::Lam { nam, bod, .. } = current {
if let Some(nam) = nam {
scope.push(nam);
}
current = bod;
}
match current {
Term::Var { nam } if scope.contains(&nam) => true,
Term::Ref { .. } => true,
term => term.is_safe(book, seen),
}
}
pub fn has_unscoped_diff(&self) -> bool {
let (declared, used) = self.unscoped_vars();
declared.difference(&used).count() != 0 || used.difference(&declared).count() != 0
}
fn is_combinator(&self) -> bool {
self.free_vars().is_empty() && !self.has_unscoped_diff() && !matches!(self, Term::Ref { .. })
}
}
impl Term {
pub fn float_combinators(&mut self, def_name: &Name, builtin: bool, combinators: &mut Combinators) {
self.go_float(0, &mut TermInfo::new(def_name.clone(), builtin, combinators));
}
fn go_float(&mut self, depth: usize, term_info: &mut TermInfo) -> Detach {
Term::recursive_call(move || match self {
Term::Lam { .. } | Term::Chn { .. } if self.is_id() => Detach::Combinator,
Term::Lam { .. } => self.float_lam(depth, term_info),
Term::Chn { .. } => self.float_lam(depth, term_info),
Term::App { .. } => self.float_app(depth, term_info),
Term::Mat { .. } => self.float_mat(depth, term_info),
Term::Var { nam } => {
term_info.request_name(nam);
Detach::Combinator
}
Term::Lnk { nam } => Detach::unscoped_var(nam.clone()),
Term::Ref { nam: def_name } if def_name == &term_info.def_name => Detach::Recursive,
_ => {
let mut detach = Detach::Combinator;
for (child, binds) in self.children_mut_with_binds() {
detach = detach & child.go_float(depth + 1, term_info);
for bind in binds {
term_info.provide(bind.as_ref());
}
}
detach
}
})
}
fn float_lam(&mut self, depth: usize, term_info: &mut TermInfo) -> Detach {
let (nam, bod, unscoped): (Option<&Name>, &mut Term, bool) = match self {
Term::Lam { nam, bod, .. } => (nam.as_ref(), bod, false),
Term::Chn { nam, bod, .. } => (nam.as_ref(), bod, true),
_ => unreachable!(),
};
let parent_scope = term_info.replace_scope(BTreeSet::new());
let mut detach = bod.go_float(depth + 1, term_info);
if unscoped {
detach = detach & Detach::unscoped_lam(nam.cloned().unwrap());
} else {
term_info.provide(nam);
}
if depth != 0 && detach.can_detach() && term_info.has_no_free_vars() {
term_info.detach_term(self);
}
term_info.merge_scope(parent_scope);
detach
}
fn float_app(&mut self, depth: usize, term_info: &mut TermInfo) -> Detach {
let Term::App { fun, arg, .. } = self else { unreachable!() };
let parent_scope = term_info.replace_scope(BTreeSet::new());
let fun_detach = fun.go_float(depth + 1, term_info);
let fun_scope = term_info.replace_scope(BTreeSet::new());
let arg_detach = arg.go_float(depth + 1, term_info);
let arg_scope = term_info.replace_scope(parent_scope);
let detach = match fun_detach {
// If the fun scope is not empty, we know the recursive ref is not in a active position,
// Because if it was an application, it would be already extracted
//
// e.g.: {recursive_ref some_var}
Detach::Recursive if !fun_scope.is_empty() => arg_detach,
Detach::Recursive if arg_scope.is_empty() && arg_detach.can_detach() => {
term_info.detach_term(self);
Detach::Combinator
}
// If the only term that is possible to detach is just a Term::Ref,
// there is no benefit to it, so that case is skipped
Detach::Recursive if !matches!(fun, box Term::Ref { .. }) => {
term_info.detach_term(fun);
arg_detach
}
_ => fun_detach & arg_detach,
};
term_info.merge_scope(fun_scope);
term_info.merge_scope(arg_scope);
detach
}
fn float_mat(&mut self, depth: usize, term_info: &mut TermInfo) -> Detach {
let Term::Mat { args, rules } = self else { unreachable!() };
let mut detach = Detach::Combinator;
for arg in args {
detach = detach & arg.go_float(depth + 1, term_info);
}
let parent_scope = term_info.replace_scope(BTreeSet::new());
for rule in rules.iter_mut() {
for pat in &rule.pats {
debug_assert!(pat.is_native_num_match());
}
let arm_detach = match rule.body.go_float(depth + 1, term_info) {
// If the recursive ref reached here, it is not in a active position
Detach::Recursive => Detach::Combinator,
detach => detach,
};
detach = detach & arm_detach;
}
term_info.merge_scope(parent_scope);
detach
}
// We don't want to detach id function, since that's not a net gain in performance or space
fn is_id(&self) -> bool {
pub fn float_children_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut Term> {
multi_iterator!(FloatIter { Zero, Two, Vec, Mat, App });
match self {
Term::Lam { nam: Some(lam_nam), bod: box Term::Var { nam: var_nam }, .. } => lam_nam == var_nam,
_ => false,
Term::App { fun, arg, .. } => {
let mut args = vec![arg.as_mut()];
let mut app = fun.as_mut();
while let Term::App { fun, arg, .. } = app {
args.push(arg);
app = fun;
}
args.push(app);
FloatIter::App(args)
}
Term::Mat { args, rules } => {
FloatIter::Mat(args.iter_mut().chain(rules.iter_mut().map(|r| &mut r.body)))
}
Term::Tup { els } | Term::Sup { els, .. } | Term::Lst { els } => FloatIter::Vec(els),
Term::Let { val: fst, nxt: snd, .. }
| Term::Use { val: fst, nxt: snd, .. }
| Term::Dup { val: fst, nxt: snd, .. }
| Term::Opx { fst, snd, .. } => FloatIter::Two([fst.as_mut(), snd.as_mut()]),
Term::Lam { bod, .. } | Term::Chn { bod, .. } => bod.float_children_mut(),
Term::Var { .. }
| Term::Lnk { .. }
| Term::Num { .. }
| Term::Nat { .. }
| Term::Str { .. }
| Term::Ref { .. }
| Term::Era
| Term::Err => FloatIter::Zero([]),
}
}
}

View File

@ -276,7 +276,8 @@ fn encode_pattern_match() {
#[test]
fn desugar_file() {
run_golden_test_dir(function_name!(), &|code, path| {
let diagnostics_cfg = DiagnosticsConfig::new(Severity::Error, true);
let mut diagnostics_cfg = DiagnosticsConfig::new(Severity::Error, true);
diagnostics_cfg.unused_definition = Severity::Allow;
let mut book = do_parse_book(code, path)?;
desugar_book(&mut book, CompileOpts::light(), diagnostics_cfg, None)?;
Ok(book.to_string())

View File

@ -0,0 +1,25 @@
(foo) = λa λb λc (foo a)
(bar) = λa λb (a bar b)
(List.ignore list ignore) =
match list {
(List.cons): (List.ignore list.tail (List.ignore))
(List.nil): 0
}
(baz) = {0 1 2 3 λa a foo}
(qux) = {0 qux}
(clax) = (λx x λa λb λc λd (clax d))
(tup) = (tup, 1, 0)
(list) = [0 list]
(A x) = (let {a b} = A; λc (a b c) x)
(B x) = (let (a, b) = B; λc (a b c) x)
(Main) = list

View File

@ -0,0 +1,10 @@
data nat = (succ pred) | zero
foo = @x match x {
succ: x.pred
zero: (bar 0)
}
bar = (foo 1)
main = (foo 0)

View File

@ -30,10 +30,12 @@ In definition 'zero_var_succ':
@succ_var = (?<#0 (a a) b> b)
@succ_var_zero = (?<a @succ_var_zero$C0 b> b)
@succ_var_zero = (?<@succ_var_zero$C0 @succ_var_zero$C1 a> a)
@succ_var_zero$C0 = a
& #0 ~ <+ #1 a>
@succ_var_zero$C0 = (<+ #2 a> a)
@succ_var_zero$C1 = (<+ #2 a> a)
@succ_zero = (?<#0 (a a) b> b)

View File

@ -14,9 +14,13 @@ input_file: tests/golden_tests/compile_file/redex_order.hvm
& @c ~ (a d)
@foo2 = a
& @a ~ (b a)
& @b ~ (c b)
& @c ~ (#0 c)
& @a ~ (@foo2$C1 a)
@foo2$C0 = a
& @c ~ (#0 a)
@foo2$C1 = a
& @b ~ (@foo2$C0 a)
@main = a
& @foo ~ (@foo2 a)

View File

@ -2,7 +2,7 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/unscoped_supercombinator.hvm
---
@Foo = ((@Foo$C0 (@Foo$C1 a)) a)
@Foo = ((@Foo$C1 (@Foo$C0 a)) a)
@Foo$C0 = (a ((a b) b))

View File

@ -4,13 +4,11 @@ input_file: tests/golden_tests/compile_file_o_all/adt_option_and.hvm
---
@None = {2 * {2 a a}}
@Option.and = ({2 @Option.and$C2 {2 @Option.and$C1_$_Option.and$C3 a}} a)
@Option.and = ({2 @Option.and$C1 {2 (* @None) a}} a)
@Option.and$C0 = {2 a (b {2 {2 [b a] c} {2 * c}})}
@Option.and$C1_$_Option.and$C3 = (* @None)
@Option.and$C2 = {2 a ({2 @Option.and$C0 {2 @Option.and$C1_$_Option.and$C3 (a b)}} b)}
@Option.and$C1 = {2 a ({2 @Option.and$C0 {2 (* @None) (a b)}} b)}
@Some = (a {2 {2 a b} {2 * b}})

View File

@ -2,9 +2,7 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file_o_all/and.hvm
---
@and = (a ({2 (b b) {2 @and$C0 (a c)}} c))
@and$C0 = (* @false)
@and = (a ({2 (b b) {2 (* @false) (a c)}} c))
@false = {2 * {2 a a}}

View File

@ -2,5 +2,8 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file_o_all/black_box_ref.hvm
---
@def_that_uses_black_box$C0 = a
& @HVM.black_box ~ (#6 a)
@main = a
& @HVM.black_box ~ (#6 <* #7 a>)
& @def_that_uses_black_box$C0 ~ <* #7 a>

View File

@ -2,9 +2,7 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file_o_all/ex2.hvm
---
@E = (* @E$C0)
@E$C0 = (* (a a))
@E = (* (* (a a)))
@I = (a (* ((a b) (* b))))

View File

@ -8,19 +8,13 @@ input_file: tests/golden_tests/compile_file_o_all/expr.hvm
@Let = (a (b (c {2 * {2 * {2 * {2 * {2 * {2 {2 a {2 b {2 c d}}} {2 * {2 * {2 * d}}}}}}}}})))
@Mul = {4 * @Mul$C1}
@Mul$C0 = {4 a {4 * a}}
@Mul$C1 = {4 * @Mul$C0}
@Mul = {4 * {4 * {4 a {4 * a}}}}
@Num = (a {2 * {2 {2 a b} {2 * {2 * {2 * {2 * {2 * {2 * {2 * b}}}}}}}}})
@Op2 = (a (b (c {2 * {2 * {2 * {2 * {2 * {2 * {2 * {2 * {2 {2 a {2 b {2 c d}}} d}}}}}}}}})))
@Sub = {4 * @Sub$C0}
@Sub$C0 = {4 a {4 * {4 * a}}}
@Sub = {4 * {4 a {4 * {4 * a}}}}
@Var = (a {2 {2 a b} {2 * {2 * {2 * {2 * {2 * {2 * {2 * {2 * b}}}}}}}}})

View File

@ -4,43 +4,29 @@ input_file: tests/golden_tests/compile_file_o_all/list_merge_sort.hvm
---
@Cons = (a (b {4 {4 a {4 b c}} {4 * c}}))
@If$C0 = (a (* a))
@Map = ({4 @Map$C0 {4 (* @Nil) a}} a)
@If$C1 = (* (a a))
@Map$C0 = {4 a {4 {4 @Map$C0 {4 (* @Nil) (b c)}} ({3 (a d) b} {4 {4 d {4 c e}} {4 * e}})}}
@Map = ({4 @Map$C0 {4 @Map$C1_$_MergePair$C4_$_Unpack$C3 a}} a)
@Merge$C0 = {4 {5 a {5 b c}} {4 {7 d {4 @Merge$C0 {4 (* @Cons) (e (f (g h)))}}} ({9 (i (a {2 (j (* j)) {2 (* (k k)) ({4 {4 l {4 m n}} {4 * n}} ({4 {4 c {4 h o}} {4 * o}} p))}})) {9 q e}} ({11 i {11 l f}} ({13 {4 @Merge$C1 {4 (* (r r)) (q ({4 {4 b {4 d s}} {4 * s}} m))}} g} p)))}}
@Map$C0 = {4 a {4 {4 @Map$C0 {4 @Map$C1_$_MergePair$C4_$_Unpack$C3 (b c)}} ({3 (a d) b} {4 {4 d {4 c e}} {4 * e}})}}
@Merge$C1 = {4 a {4 b (c ({4 @Merge$C0 {4 (* @Cons) (c (a (b d)))}} d))}}
@Map$C1_$_MergePair$C4_$_Unpack$C3 = (* @Nil)
@MergePair$C0 = (* (a {4 {4 a {4 @Nil b}} {4 * b}}))
@Merge$C0 = {4 {5 a {5 b c}} {4 {7 d {4 @Merge$C0 {4 @Merge$C1 (e (f (g h)))}}} ({9 (i (a {2 @If$C0 {2 @If$C1 ({4 {4 j {4 k l}} {4 * l}} ({4 {4 c {4 h m}} {4 * m}} n))}})) {9 o e}} ({11 i {11 j f}} ({13 {4 @Merge$C2 {4 @Merge$C3 (o ({4 {4 b {4 d p}} {4 * p}} k))}} g} n)))}}
@MergePair$C1 = {4 a {4 {4 @MergePair$C2 {4 (* @Nil) (b c)}} ({15 d b} ({4 @Merge$C1 {4 (* (e e)) (d (a f))}} {4 {4 f {4 c g}} {4 * g}}))}}
@Merge$C1 = (* @Cons)
@Merge$C2 = {4 a {4 b (c ({4 @Merge$C0 {4 @Merge$C1 (c (a (b d)))}} d))}}
@Merge$C3 = (* (a a))
@MergePair$C0 = {4 a {4 {4 @MergePair$C3 {4 @Map$C1_$_MergePair$C4_$_Unpack$C3 (b c)}} ({15 d b} ({4 @Merge$C2 {4 @Merge$C3 (d (a e))}} {4 {4 e {4 c f}} {4 * f}}))}}
@MergePair$C1 = (a {4 {4 a {4 @Nil b}} {4 * b}})
@MergePair$C2 = (* @MergePair$C1)
@MergePair$C3 = {4 a {4 {4 @MergePair$C0 {4 @MergePair$C2 (b (a c))}} (b c)}}
@MergePair$C2 = {4 a {4 {4 @MergePair$C1 {4 @MergePair$C0 (b (a c))}} (b c)}}
@Nil = {4 * {4 a a}}
@Pure = (a {4 {4 a {4 @Nil b}} {4 * b}})
@Unpack = (a ({4 @Unpack$C2 {4 @Map$C1_$_MergePair$C4_$_Unpack$C3 (a b)}} b))
@Unpack = (a ({4 @Unpack$C1 {4 (* @Nil) (a b)}} b))
@Unpack$C0 = {4 a {4 {4 @MergePair$C3 {4 @Map$C1_$_MergePair$C4_$_Unpack$C3 (b {4 @Unpack$C0 {4 @Unpack$C1 (c (d e))}})}} ({17 c {15 f b}} ({4 @Merge$C2 {4 @Merge$C3 (f (a d))}} e))}}
@Unpack$C0 = {4 a {4 {4 @MergePair$C2 {4 (* @Nil) (b {4 @Unpack$C0 {4 (* (c c)) (d (e f))}})}} ({17 d {15 g b}} ({4 @Merge$C1 {4 (* (h h)) (g (a e))}} f))}}
@Unpack$C1 = (* (a a))
@Unpack$C2 = {4 a {4 {4 @Unpack$C0 {4 @Unpack$C1 (b (a c))}} (b c)}}
@Unpack$C1 = {4 a {4 {4 @Unpack$C0 {4 (* (b b)) (c (a d))}} (c d)}}
@main = (a (b c))
& @Unpack ~ (a (d c))

View File

@ -2,9 +2,9 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file_o_all/num_pattern_with_var.hvm
---
@Foo = ({2 (* #0) {2 @Foo$C1 a}} a)
@Foo = ({2 (* #0) {2 @Foo$C0 a}} a)
@Foo$C1 = (?<#0 (a a) b> b)
@Foo$C0 = (?<#0 (a a) b> b)
@main = a
& @Foo ~ (@true (#3 a))

View File

@ -2,9 +2,9 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file_o_all/recursive_combinator_inactive.hvm
---
@Foo = (?<{3 @Foo @Foo} @Foo$C0 a> a)
@Foo = (?<@Foo$C0 (a (* a)) b> b)
@Foo$C0 = (a (* a))
@Foo$C0 = {3 @Foo @Foo}
@main = a
& @Foo ~ (#0 a)

View File

@ -28,9 +28,7 @@ For let expressions where the variable is non-linear (used more than once), you
See here for more info: https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md.
@long_name_that_truncates = (* @long_name_that_truncates$C0)
@long_name_that_truncates$C0 = (* @long_name_that_truncates)
@long_name_that_truncates = (* (* @long_name_that_truncates))
@main = a
& @long_name_that_truncates ~ ((b b) a)

View File

@ -4,13 +4,7 @@ input_file: tests/golden_tests/compile_file_o_all/scrutinee_reconstruction.hvm
---
@None = {2 * {2 a a}}
@Option.or = ({3 {2 @Option.or$C1 {2 @Option.or$C2 (a b)}} a} b)
@Option.or$C0 = (a (* a))
@Option.or$C1 = {2 * @Option.or$C0}
@Option.or$C2 = (* (a a))
@Option.or = ({3 {2 {2 * (a (* a))} {2 (* (b b)) (c d)}} c} d)
@Some = (a {2 {2 a b} {2 * b}})

View File

@ -2,17 +2,7 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file_o_all/weekday.hvm
---
@Saturday = {2 * @Saturday$C4}
@Saturday$C0 = {2 a {2 * a}}
@Saturday$C1 = {2 * @Saturday$C0}
@Saturday$C2 = {2 * @Saturday$C1}
@Saturday$C3 = {2 * @Saturday$C2}
@Saturday$C4 = {2 * @Saturday$C3}
@Saturday = {2 * {2 * {2 * {2 * {2 * {2 a {2 * a}}}}}}}
@main = a
& (b b) ~ (@Saturday a)

View File

@ -0,0 +1,39 @@
---
source: tests/golden_tests.rs
input_file: tests/golden_tests/desugar_file/combinators.hvm
---
(foo) = λa λ* λ* (foo a)
(bar) = λa λb (a bar b)
(List.ignore) = λa λ* #List (a List.ignore$C0 0)
(baz) = {0 1 2 3 λa a foo}
(qux) = {0 qux}
(clax) = (λa a clax$C0)
(tup) = (tup, 1, 0)
(list) = (List.cons 0 list$C0)
(A) = λa (A$C0 a)
(B) = λa (B$C0 a)
(Main) = list
(List.cons) = λa λb #List λc #List λ* #List (c a b)
(List.nil) = #List λ* #List λb b
(A$C0) = let {b c} = A; λd (b c d)
(B$C0) = let (c, d) = B; λe (c d e)
(List.ignore$C0) = #List λ* #List λd (List.ignore d List.ignore)
(clax$C0) = λ* λ* λ* λe (clax e)
(list$C0) = (List.cons list List.nil)

View File

@ -0,0 +1,15 @@
---
source: tests/golden_tests.rs
input_file: tests/golden_tests/desugar_file/deref_loop.hvm
---
(foo) = λa #nat (a #nat λb b foo$C0)
(bar) = (foo 1)
(main) = (foo 0)
(succ) = λa #nat λb #nat λ* #nat (b a)
(zero) = #nat λ* #nat λb b
(foo$C0) = (bar 0)

View File

@ -2,14 +2,12 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/mutual_recursion/len.hvm
---
@Len = ({2 @Len$C1 {2 #0 a}} a)
@Len = ({2 @Len$C0 {2 #0 a}} a)
@Len$C0 = {2 a b}
@Len$C0 = {2 * {2 a b}}
& #1 ~ <+ c b>
& @Len ~ (a c)
@Len$C1 = {2 * @Len$C0}
@List.cons = (a (b {2 {2 a {2 b c}} {2 * c}}))
@List.nil = {2 * {2 a a}}