Skip to content

Commit

Permalink
Merge pull request #644 from HigherOrderCO/643-references-in-main-are…
Browse files Browse the repository at this point in the history
…nt-expanded-when-theyre-inside-lists

#642 #643 Make `expand_main` deal with more cases (extracted combinators, constructors)
  • Loading branch information
Nicolas Abril authored Jul 24, 2024
2 parents b53568a + b3ccb75 commit 2232a70
Show file tree
Hide file tree
Showing 12 changed files with 150 additions and 56 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ 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])
- Expand references inside constructors in the main function. ([#643][gh-643])

### Added

Expand Down Expand Up @@ -406,4 +408,6 @@ 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
[gh-643]: https://github.com/HigherOrderCO/Bend/issues/643
[Unreleased]: https://github.com/HigherOrderCO/Bend/compare/0.2.36...HEAD
4 changes: 3 additions & 1 deletion src/fun/transform/definition_pruning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ type Definitions = HashMap<Name, Used>;

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();

Expand Down
57 changes: 55 additions & 2 deletions src/fun/transform/expand_main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -32,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<Name>, globals_count: &mut usize) {
fn expand_ref_return(&mut self, book: &Book, seen: &mut Vec<Name>, globals_count: &mut usize) {
maybe_grow(|| match self {
Term::Ref { nam } => {
if seen.contains(nam) {
Expand All @@ -51,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, .. }
Expand All @@ -59,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 { .. }
Expand All @@ -73,6 +97,35 @@ impl Term {
| Term::Err => {}
})
}

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);
}
})
}

/// 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 {
Expand Down
4 changes: 3 additions & 1 deletion src/fun/transform/float_combinators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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() };
Expand Down
100 changes: 52 additions & 48 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String> {
fn filter_hvm_output(
mut stream: impl std::io::Read + Send,
mut output: impl std::io::Write + Send,
) -> Result<String, String> {
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())
Expand Down Expand Up @@ -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<String, String> {
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,
Expand Down
5 changes: 5 additions & 0 deletions tests/golden_tests/run_file/expand_main_combinator.bend
Original file line number Diff line number Diff line change
@@ -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)
6 changes: 6 additions & 0 deletions tests/golden_tests/run_file/expand_main_list.bend
Original file line number Diff line number Diff line change
@@ -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]
2 changes: 1 addition & 1 deletion tests/golden_tests/run_file/names_hyphen_toplevel.bend
Original file line number Diff line number Diff line change
Expand Up @@ -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)
2 changes: 1 addition & 1 deletion tests/snapshots/desugar_file__combinators.bend.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
9 changes: 9 additions & 0 deletions tests/snapshots/run_file__expand_main_combinator.bend.snap
Original file line number Diff line number Diff line change
@@ -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)
9 changes: 9 additions & 0 deletions tests/snapshots/run_file__expand_main_list.bend.snap
Original file line number Diff line number Diff line change
@@ -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]
4 changes: 2 additions & 2 deletions tests/snapshots/run_file__names_hyphen_toplevel.bend.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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)

0 comments on commit 2232a70

Please sign in to comment.