diff --git a/src/util.rs b/src/util.rs index 81553b5a..92f1810b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,4 +1,7 @@ -use crate::run::Rewrites; +use crate::{ + ast::{Net, Tree}, + run::Rewrites, +}; use std::time::Duration; /// Creates a variable uniquely identified by `id`. @@ -15,6 +18,7 @@ pub(crate) fn create_var(mut id: usize) -> String { } /// Inverse function of [`create_var`]. +/// /// Returns None when the provided string is not an output of /// `create_var`. pub fn var_to_num(s: &str) -> Option { @@ -151,44 +155,64 @@ macro_rules! deref { }; } -impl crate::ast::Net { - /// Transforms the net `x & ...` into `y & x ~ (arg y) & ...` - pub fn with_argument(&mut self, arg: crate::ast::Tree) { - use crate::ast::Tree; - let mut fresh = 0usize; - fn ensure_no_conflicts(tree: &Tree, fresh: &mut usize) { - match tree { - Tree::Ctr { lft, rgt, .. } | Tree::Op2 { lft, rgt, .. } | Tree::Mat { sel: lft, ret: rgt } => { - ensure_no_conflicts(lft, fresh); - ensure_no_conflicts(rgt, fresh); - } - Tree::Op1 { rgt, .. } => { - ensure_no_conflicts(rgt, fresh); - } - Tree::Var { nam } => { - if let Some(var_num) = var_to_num(nam) { - *fresh = (*fresh).max(var_num); - } +pub(crate) use deref; + +impl Tree { + /// Increases `fresh` until `create_var(*fresh)` does not conflict + /// with a [`Tree::Var`] in `tree` + /// + /// This function can be called multiple times with many trees to + /// ensure that `fresh` does not conflict with any of them. + pub(crate) fn ensure_no_conflicts(&self, fresh: &mut usize) { + match self { + Tree::Var { nam } => { + if let Some(var_num) = var_to_num(nam) { + *fresh = (*fresh).max(var_num); } - _ => (), } + // Recurse on children + Tree::Ctr { lft, rgt, .. } | Tree::Op2 { lft, rgt, .. } | Tree::Mat { sel: lft, ret: rgt } => { + lft.ensure_no_conflicts(fresh); + rgt.ensure_no_conflicts(fresh); + } + Tree::Op1 { rgt, .. } => { + rgt.ensure_no_conflicts(fresh); + } + Tree::Era => (), + Tree::Num { .. } => (), + Tree::Ref { .. } => (), } - ensure_no_conflicts(&self.root, &mut fresh); - for (l, r) in &self.rdex { - ensure_no_conflicts(l, &mut fresh); - ensure_no_conflicts(r, &mut fresh); + } +} + +impl Net { + pub(crate) fn ensure_no_conflicts(&self, fresh: &mut usize) { + self.root.ensure_no_conflicts(fresh); + for (a, b) in &self.rdex { + a.ensure_no_conflicts(fresh); + b.ensure_no_conflicts(fresh); } + } + /// Transforms the net `x & ...` into `y & x ~ (arg y) & ...` + /// + /// The result is equivalent a λ-calculus application. Thus, + /// if the net is a λ-calculus term, then this function will + /// apply an argument to it. + #[allow(dead_code)] // used in tests + pub(crate) fn with_argument(&mut self, arg: Tree) { + let mut fresh = 0usize; + self.ensure_no_conflicts(&mut fresh); + arg.ensure_no_conflicts(&mut fresh); + let fresh_str = create_var(fresh + 1); let fun = core::mem::take(&mut self.root); - let oth = Tree::Ctr { lab: 0, lft: Box::new(arg), rgt: Box::new(Tree::Var { nam: fresh_str.clone() }) }; + let app = Tree::Ctr { lab: 0, lft: Box::new(arg), rgt: Box::new(Tree::Var { nam: fresh_str.clone() }) }; self.root = Tree::Var { nam: fresh_str }; - self.rdex.push((fun, oth)); + self.rdex.push((fun, app)); } } -pub(crate) use deref; - pub fn show_rewrites(rwts: &Rewrites) -> String { format!( "{}{}{}{}{}{}", diff --git a/tests/api.rs b/tests/api.rs new file mode 100644 index 00000000..53c5fefc --- /dev/null +++ b/tests/api.rs @@ -0,0 +1,59 @@ +//! Tests for front-facing APIs and interfaces + +use hvmc::{ + ast::{Book, Net, Tree}, + host::Host, +}; +use insta::assert_display_snapshot; + +#[test] +fn test_with_argument() { + use hvmc::run; + fn eval_with_args(fun: &str, args: &[&str]) -> Net { + let area = run::Net::::init_heap(1 << 10); + + let mut fun: Net = fun.parse().unwrap(); + for arg in args { + let arg: Tree = arg.parse().unwrap(); + fun.with_argument(arg) + } + // TODO: When feature/sc-472/argument-passing, use encode_net instead. + let mut book = Book::default(); + book.nets.insert("main".into(), fun); + let host = Host::new(&book); + + let mut rnet = run::Net::::new(&area); + rnet.boot(&host.defs["main"]); + rnet.normal(); + let got_result = host.readback(&rnet); + got_result + } + assert_display_snapshot!( + eval_with_args("(a a)", &vec!["(a a)"]), + @"(a a)" + ); + assert_display_snapshot!( + eval_with_args("b & (a b) ~ a", &vec!["(a a)"]), + @"a" + ); + assert_display_snapshot!( + eval_with_args("(z0 z0)", &vec!["(z1 z1)"]), + @"(a a)" + ); + assert_display_snapshot!( + eval_with_args("(* #1)", &vec!["(a a)"]), + @"#1" + ); + assert_display_snapshot!( + eval_with_args("(<+ a b> (a b))", &vec!["#1", "#2"]), + @"#3" + ); + assert_display_snapshot!( + eval_with_args("(<* a b> (a b))", &vec!["#2", "#3"]), + @"#6" + ); + assert_display_snapshot!( + eval_with_args("(<* a b> (a b))", &vec!["#2"]), + @"(<2* a> a)" + ); +} diff --git a/tests/lists.rs b/tests/lists.rs index f68269e6..523ba5a4 100644 --- a/tests/lists.rs +++ b/tests/lists.rs @@ -7,10 +7,10 @@ fn list_got(index: u32) -> Book { let code = load_file("list_put_got.hvmc"); let mut book = parse_core(&code); println!("{:#?}", book.keys().collect::>()); - let mut def = book.get_mut("GenGotIndex").unwrap(); - core_apply(&mut def, hvmc::ast::Tree::Ref { nam: format!("S{index}") }); - let mut def = book.get_mut("main").unwrap(); - core_apply(&mut def, hvmc::ast::Tree::Ref { nam: format!("GenGotIndex") }); + let def = book.get_mut("GenGotIndex").unwrap(); + def.with_argument(hvmc::ast::Tree::Ref { nam: format!("S{index}") }); + let def = book.get_mut("main").unwrap(); + def.with_argument(hvmc::ast::Tree::Ref { nam: format!("GenGotIndex") }); book } diff --git a/tests/loaders/mod.rs b/tests/loaders/mod.rs index 4094e9be..afbff8ca 100644 --- a/tests/loaders/mod.rs +++ b/tests/loaders/mod.rs @@ -8,14 +8,6 @@ pub fn load_file(file: &str) -> String { fs::read_to_string(path).unwrap() } -pub fn core_apply(net: &mut Net, arg: Tree) { - let fun = core::mem::take(&mut net.root); - let var = format!("$_TEST_APP{:p}", net); - let oth = Tree::Ctr { lab: 0, lft: Box::new(arg), rgt: Box::new(Tree::Var { nam: var.clone() }) }; - net.root = Tree::Var { nam: var }; - net.rdex.push((fun, oth)); -} - // Parses code and generate Book from hvm-core syntax pub fn parse_core(code: &str) -> Book { code.parse().unwrap()