From 435d3ca14383a0ad1b2879d635774f9319eaec9b Mon Sep 17 00:00:00 2001 From: Nicolas Abril Date: Wed, 24 Jul 2024 12:47:39 +0200 Subject: [PATCH 1/3] Expand float_combinators defs inside main --- CHANGELOG.md | 2 + src/fun/transform/definition_pruning.rs | 4 +- src/fun/transform/expand_main.rs | 16 +++ src/fun/transform/float_combinators.rs | 4 +- src/lib.rs | 100 +++++++++--------- .../run_file/expand_main_combinator.bend | 5 + .../desugar_file__combinators.bend.snap | 2 +- ...run_file__expand_main_combinator.bend.snap | 9 ++ 8 files changed, 91 insertions(+), 51 deletions(-) create mode 100644 tests/golden_tests/run_file/expand_main_combinator.bend create mode 100644 tests/snapshots/run_file__expand_main_combinator.bend.snap diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b340dcca..47499bdba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project does not currently adhere to a particular versioning scheme. - Fix variable binding in pattern matching when the irrefutable pattern optimization occurs. ([#618][gh-618]) - Don't warn on unused generated definitions. ([#514][gh-514]) - Fix local definitions not being desugared properly. ([#623][gh-623]) +- Expand references to functions generated by the `float_combinators` pass inside the main function. ([#642][gh-642]) ### Added @@ -406,4 +407,5 @@ and this project does not currently adhere to a particular versioning scheme. [gh-620]: https://github.com/HigherOrderCO/Bend/issues/620 [gh-621]: https://github.com/HigherOrderCO/Bend/issues/621 [gh-623]: https://github.com/HigherOrderCO/Bend/issues/623 +[gh-642]: https://github.com/HigherOrderCO/Bend/issues/642 [Unreleased]: https://github.com/HigherOrderCO/Bend/compare/0.2.36...HEAD diff --git a/src/fun/transform/definition_pruning.rs b/src/fun/transform/definition_pruning.rs index 0e9ba616b..11a575802 100644 --- a/src/fun/transform/definition_pruning.rs +++ b/src/fun/transform/definition_pruning.rs @@ -20,7 +20,9 @@ type Definitions = HashMap; impl Ctx<'_> { /// If `prune_all`, removes all unused definitions and adts starting from Main. - /// Otherwise, prunes only the builtins not accessible from any non-built-in definition + /// Otherwise, prunes only the builtins not accessible from any non-built-in definition. + /// + /// Emits unused definition warnings. pub fn prune(&mut self, prune_all: bool) { let mut used = Definitions::new(); diff --git a/src/fun/transform/expand_main.rs b/src/fun/transform/expand_main.rs index 8b07f4b90..dc80cb07a 100644 --- a/src/fun/transform/expand_main.rs +++ b/src/fun/transform/expand_main.rs @@ -18,6 +18,9 @@ impl Book { let mut seen = vec![self.entrypoint.as_ref().unwrap().clone()]; main_bod.expand_ref_return(self, &mut seen, &mut 0); + // Undo the `float_combinators` pass for main, to recover the strictness of the main function. + main_bod.expand_floated_combinators(self); + let main = self.defs.get_mut(self.entrypoint.as_ref().unwrap()).unwrap(); main.rule_mut().body = main_bod; } @@ -73,6 +76,19 @@ impl Term { | Term::Err => {} }) } + + pub fn expand_floated_combinators(&mut self, book: &Book) { + maybe_grow(|| { + if let Term::Ref { nam } = self { + if nam.contains(super::float_combinators::NAME_SEP) { + *self = book.defs.get(nam).unwrap().rule().body.clone(); + } + } + for child in self.children_mut() { + child.expand_floated_combinators(book); + } + }) + } } impl Term { diff --git a/src/fun/transform/float_combinators.rs b/src/fun/transform/float_combinators.rs index a19af4747..2bef4d381 100644 --- a/src/fun/transform/float_combinators.rs +++ b/src/fun/transform/float_combinators.rs @@ -4,6 +4,8 @@ use crate::{ }; use std::collections::{BTreeMap, HashSet}; +pub const NAME_SEP: &str = "__C"; + impl Book { /// Extracts combinator terms into new definitions. /// @@ -110,7 +112,7 @@ impl Term { /// Inserts a new definition for the given term in the combinators map. fn float(&mut self, ctx: &mut FloatCombinatorsCtx, def_name: &Name, builtin: bool, is_safe: bool) { - let comb_name = Name::new(format!("{}__C{}", def_name, ctx.name_gen)); + let comb_name = Name::new(format!("{}{}{}", def_name, NAME_SEP, ctx.name_gen)); ctx.name_gen += 1; let comb_ref = Term::Ref { nam: comb_name.clone() }; diff --git a/src/lib.rs b/src/lib.rs index 1f1435791..e5dfe9a33 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -206,54 +206,6 @@ pub fn readback_hvm_net( /// Runs an HVM book by invoking HVM as a subprocess. fn run_hvm(book: &::hvm::ast::Book, cmd: &str, run_opts: &RunOpts) -> Result { - fn filter_hvm_output( - mut stream: impl std::io::Read + Send, - mut output: impl std::io::Write + Send, - ) -> Result { - let mut capturing = false; - let mut result = String::new(); - let mut buf = [0u8; 1024]; - loop { - let num_read = match stream.read(&mut buf) { - Ok(n) => n, - Err(e) => { - eprintln!("{e}"); - break; - } - }; - if num_read == 0 { - break; - } - let new_buf = &buf[..num_read]; - // TODO: Does this lead to broken characters if printing too much at once? - let new_str = String::from_utf8_lossy(new_buf); - if capturing { - // Store the result - result.push_str(&new_str); - } else if let Some((before, after)) = new_str.split_once(HVM_OUTPUT_END_MARKER) { - // If result started in the middle of the buffer, print what came before and start capturing. - if let Err(e) = output.write_all(before.as_bytes()) { - eprintln!("Error writing HVM output. {e}"); - }; - result.push_str(after); - capturing = true; - } else { - // Otherwise, don't capture anything - if let Err(e) = output.write_all(new_buf) { - eprintln!("Error writing HVM output. {e}"); - } - } - } - - if capturing { - Ok(result) - } else { - output.flush().map_err(|e| format!("Error flushing HVM output. {e}"))?; - let msg = "HVM output had no result (An error likely occurred)".to_string(); - Err(msg) - } - } - let out_path = ".out.hvm"; std::fs::write(out_path, hvm_book_show_pretty(book)).map_err(|x| x.to_string())?; let mut process = std::process::Command::new(run_opts.hvm_path.clone()) @@ -291,6 +243,58 @@ fn parse_hvm_output(out: &str) -> Result<(::hvm::ast::Net, String), String> { Ok((net, stats.to_string())) } +/// Filters the output from HVM, separating user output from the +/// result, used for readback and displaying stats. +/// +/// Buffers the output from HVM to try to parse it. +fn filter_hvm_output( + mut stream: impl std::io::Read + Send, + mut output: impl std::io::Write + Send, +) -> Result { + let mut capturing = false; + let mut result = String::new(); + let mut buf = [0u8; 1024]; + loop { + let num_read = match stream.read(&mut buf) { + Ok(n) => n, + Err(e) => { + eprintln!("{e}"); + break; + } + }; + if num_read == 0 { + break; + } + let new_buf = &buf[..num_read]; + // TODO: Does this lead to broken characters if printing too much at once? + let new_str = String::from_utf8_lossy(new_buf); + if capturing { + // Store the result + result.push_str(&new_str); + } else if let Some((before, after)) = new_str.split_once(HVM_OUTPUT_END_MARKER) { + // If result started in the middle of the buffer, print what came before and start capturing. + if let Err(e) = output.write_all(before.as_bytes()) { + eprintln!("Error writing HVM output. {e}"); + }; + result.push_str(after); + capturing = true; + } else { + // Otherwise, don't capture anything + if let Err(e) = output.write_all(new_buf) { + eprintln!("Error writing HVM output. {e}"); + } + } + } + + if capturing { + Ok(result) + } else { + output.flush().map_err(|e| format!("Error flushing HVM output. {e}"))?; + let msg = "HVM output had no result (An error likely occurred)".to_string(); + Err(msg) + } +} + #[derive(Clone, Debug)] pub struct RunOpts { pub linear_readback: bool, diff --git a/tests/golden_tests/run_file/expand_main_combinator.bend b/tests/golden_tests/run_file/expand_main_combinator.bend new file mode 100644 index 000000000..8ab184474 --- /dev/null +++ b/tests/golden_tests/run_file/expand_main_combinator.bend @@ -0,0 +1,5 @@ +# It should reduce to `@a (a 1 2)` if main is expanded correctly. +# (ctr 1 2) should not be extracted from main into a separate function. +main = my_fn +my_fn = (@x x (ctr 1 2)) +ctr = @a @b @x (x a b) \ No newline at end of file diff --git a/tests/snapshots/desugar_file__combinators.bend.snap b/tests/snapshots/desugar_file__combinators.bend.snap index 8a2c6c3cd..58ce05b16 100644 --- a/tests/snapshots/desugar_file__combinators.bend.snap +++ b/tests/snapshots/desugar_file__combinators.bend.snap @@ -22,7 +22,7 @@ input_file: tests/golden_tests/desugar_file/combinators.bend (B) = λa (B__C0 a) -(Main) = (List/Cons 0 list__C0) +(Main) = (List/Cons 0 (List/Cons list List/Nil)) (List/Nil) = λa (a List/Nil/tag) diff --git a/tests/snapshots/run_file__expand_main_combinator.bend.snap b/tests/snapshots/run_file__expand_main_combinator.bend.snap new file mode 100644 index 000000000..1c1d813e1 --- /dev/null +++ b/tests/snapshots/run_file__expand_main_combinator.bend.snap @@ -0,0 +1,9 @@ +--- +source: tests/golden_tests.rs +input_file: tests/golden_tests/run_file/expand_main_combinator.bend +--- +NumScott: +λa (a 1 2) + +Scott: +λa (a 1 2) From 87cef6054de00e4e58abc99a552e25a282cfe654 Mon Sep 17 00:00:00 2001 From: Nicolas Abril Date: Wed, 24 Jul 2024 17:01:55 +0200 Subject: [PATCH 2/3] Expand references in main inside constructors --- CHANGELOG.md | 4 +- src/fun/transform/expand_main.rs | 43 +++++++++++++++++-- .../run_file/expand_main_list.bend | 6 +++ .../run_file/names_hyphen_toplevel.bend | 2 +- .../run_file__expand_main_list.bend.snap | 9 ++++ .../run_file__names_hyphen_toplevel.bend.snap | 4 +- 6 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 tests/golden_tests/run_file/expand_main_list.bend create mode 100644 tests/snapshots/run_file__expand_main_list.bend.snap diff --git a/CHANGELOG.md b/CHANGELOG.md index 47499bdba..b735a805b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,10 @@ and this project does not currently adhere to a particular versioning scheme. ### Fixed - Fix variable binding in pattern matching when the irrefutable pattern optimization occurs. ([#618][gh-618]) -- Don't warn on unused generated definitions. ([#514][gh-514]) +- Don't warn on unused generated definitions. ([git #514][gh-514]) - Fix local definitions not being desugared properly. ([#623][gh-623]) - Expand references to functions generated by the `float_combinators` pass inside the main function. ([#642][gh-642]) +- Expand references inside constructors in the main function. ([#643][gh-643]) ### Added @@ -408,4 +409,5 @@ and this project does not currently adhere to a particular versioning scheme. [gh-621]: https://github.com/HigherOrderCO/Bend/issues/621 [gh-623]: https://github.com/HigherOrderCO/Bend/issues/623 [gh-642]: https://github.com/HigherOrderCO/Bend/issues/642 +[gh-643]: https://github.com/HigherOrderCO/Bend/issues/643 [Unreleased]: https://github.com/HigherOrderCO/Bend/compare/0.2.36...HEAD diff --git a/src/fun/transform/expand_main.rs b/src/fun/transform/expand_main.rs index dc80cb07a..c2126eb52 100644 --- a/src/fun/transform/expand_main.rs +++ b/src/fun/transform/expand_main.rs @@ -35,7 +35,7 @@ impl Term { /// - When main returns a pair or superposition and one of its elements is a reference. /// /// Only expand recursive functions once. - pub fn expand_ref_return(&mut self, book: &Book, seen: &mut Vec, globals_count: &mut usize) { + fn expand_ref_return(&mut self, book: &Book, seen: &mut Vec, globals_count: &mut usize) { maybe_grow(|| match self { Term::Ref { nam } => { if seen.contains(nam) { @@ -54,6 +54,28 @@ impl Term { el.expand_ref_return(book, seen, globals_count); } } + // If an application is just a constructor, we expand the arguments. + // That way we can write programs like + // `main = [do_thing1, do_thing2, do_thing3]` + Term::App { .. } => { + let (fun, args) = self.multi_arg_app(); + if let Term::Ref { nam } = fun { + if book.ctrs.contains_key(nam) { + for arg in args { + // If the argument is a 0-ary constructor, we don't need to expand it. + if let Term::Ref { nam } = arg { + if let Some(adt_nam) = book.ctrs.get(nam) { + if book.adts.get(adt_nam).unwrap().ctrs.get(nam).unwrap().is_empty() { + continue; + } + } + } + // Otherwise, we expand the argument. + arg.expand_ref_return(book, seen, globals_count); + } + } + } + } Term::Lam { bod: nxt, .. } | Term::With { bod: nxt, .. } | Term::Open { bod: nxt, .. } @@ -62,7 +84,6 @@ impl Term { | Term::Use { nxt, .. } => nxt.expand_ref_return(book, seen, globals_count), Term::Var { .. } | Term::Link { .. } - | Term::App { .. } | Term::Num { .. } | Term::Nat { .. } | Term::Str { .. } @@ -77,7 +98,7 @@ impl Term { }) } - pub fn expand_floated_combinators(&mut self, book: &Book) { + fn expand_floated_combinators(&mut self, book: &Book) { maybe_grow(|| { if let Term::Ref { nam } = self { if nam.contains(super::float_combinators::NAME_SEP) { @@ -89,6 +110,22 @@ impl Term { } }) } + + /// Read the term as an n-ary application. + fn multi_arg_app(&mut self) -> (&mut Term, Vec<&mut Term>) { + fn go<'a>(term: &'a mut Term, args: &mut Vec<&'a mut Term>) -> &'a mut Term { + match term { + Term::App { fun, arg, .. } => { + args.push(arg); + go(fun, args) + } + _ => term, + } + } + let mut args = vec![]; + let fun = go(self, &mut args); + (fun, args) + } } impl Term { diff --git a/tests/golden_tests/run_file/expand_main_list.bend b/tests/golden_tests/run_file/expand_main_list.bend new file mode 100644 index 000000000..f63173389 --- /dev/null +++ b/tests/golden_tests/run_file/expand_main_list.bend @@ -0,0 +1,6 @@ +# Should return [1, 2, 3] +fn1 = (@x x 1) +fn2 = (@x x 2) +fn3 = (@x x 3) + +main = [fn1, fn2, fn3] diff --git a/tests/golden_tests/run_file/names_hyphen_toplevel.bend b/tests/golden_tests/run_file/names_hyphen_toplevel.bend index 059a7c442..22d01ffdf 100644 --- a/tests/golden_tests/run_file/names_hyphen_toplevel.bend +++ b/tests/golden_tests/run_file/names_hyphen_toplevel.bend @@ -2,4 +2,4 @@ type Foo-Bar = (Baz-Qux field-hyph) fun-with-hyphen = 1 -main = (Foo-Bar/Baz-Qux fun-with-hyphen) +main = @x (x (Foo-Bar/Baz-Qux 1) fun-with-hyphen) diff --git a/tests/snapshots/run_file__expand_main_list.bend.snap b/tests/snapshots/run_file__expand_main_list.bend.snap new file mode 100644 index 000000000..9f8696ac1 --- /dev/null +++ b/tests/snapshots/run_file__expand_main_list.bend.snap @@ -0,0 +1,9 @@ +--- +source: tests/golden_tests.rs +input_file: tests/golden_tests/run_file/expand_main_list.bend +--- +NumScott: +[1, 2, 3] + +Scott: +[1, 2, 3] diff --git a/tests/snapshots/run_file__names_hyphen_toplevel.bend.snap b/tests/snapshots/run_file__names_hyphen_toplevel.bend.snap index 1e130e98b..91b8bf59e 100644 --- a/tests/snapshots/run_file__names_hyphen_toplevel.bend.snap +++ b/tests/snapshots/run_file__names_hyphen_toplevel.bend.snap @@ -3,7 +3,7 @@ source: tests/golden_tests.rs input_file: tests/golden_tests/run_file/names_hyphen_toplevel.bend --- NumScott: -λa (a Foo-Bar/Baz-Qux/tag fun-with-hyphen) +λa (a λb (b Foo-Bar/Baz-Qux/tag 1) fun-with-hyphen) Scott: -λa (a fun-with-hyphen) +λa (a λb (b 1) fun-with-hyphen) From b3ccb756069526a7369c3d582bf99e172aa1465d Mon Sep 17 00:00:00 2001 From: Nicolas Abril Date: Wed, 24 Jul 2024 18:13:04 +0200 Subject: [PATCH 3/3] Remove git command from CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b735a805b..4596a183e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project does not currently adhere to a particular versioning scheme. ### Fixed - Fix variable binding in pattern matching when the irrefutable pattern optimization occurs. ([#618][gh-618]) -- Don't warn on unused generated definitions. ([git #514][gh-514]) +- Don't warn on unused generated definitions. ([#514][gh-514]) - Fix local definitions not being desugared properly. ([#623][gh-623]) - Expand references to functions generated by the `float_combinators` pass inside the main function. ([#642][gh-642]) - Expand references inside constructors in the main function. ([#643][gh-643])