diff --git a/Cargo.lock b/Cargo.lock index 607411317..700d2d9b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,9 +70,9 @@ checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" [[package]] name = "cc" -version = "1.0.89" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0ba8f7aaa012f30d5b2861462f6708eccd49c3c39863fe083a308035f63d723" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" [[package]] name = "cfg-if" @@ -92,9 +92,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.1" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" +checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" dependencies = [ "clap_builder", "clap_derive", @@ -102,9 +102,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.1" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", @@ -202,7 +202,7 @@ checksum = "809e18805660d7b6b2e2b9f316a5099521b5998d5cba4dda11b5157a21aaef03" [[package]] name = "hvm-core" version = "0.2.19" -source = "git+https://github.com/HigherOrderCO/hvm-core#32f80ba12145a318d6c84047576c4b4cd01a1760" +source = "git+https://github.com/HigherOrderCO/hvm-core.git?branch=feature/sc-506/smart-pointer-def-api#80b0a403b836a51a8b0c57775188f84f7c186c72" dependencies = [ "clap", "nohash-hasher", @@ -330,9 +330,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] diff --git a/Cargo.toml b/Cargo.toml index c87c50156..3c9c0e70b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ cli = ["dep:clap"] chumsky = { version = "= 1.0.0-alpha.4", features = ["label"] } clap = { version = "4.4.1", features = ["derive"], optional = true } highlight_error = "0.1.1" -hvm-core = { git = "https://github.com/HigherOrderCO/hvm-core" } +hvm-core = { git = "https://github.com/HigherOrderCO/hvm-core.git", branch = "feature/sc-506/smart-pointer-def-api" } indexmap = "2.2.3" interner = "0.2.1" itertools = "0.11.0" diff --git a/docs/builtin-defs.md b/docs/builtin-defs.md index dca708d6a..838e156fa 100644 --- a/docs/builtin-defs.md +++ b/docs/builtin-defs.md @@ -2,6 +2,16 @@ HVM-lang includes some built-in definitions that can make certain tasks easier, such as debugging, benchmarking, etc. These definitions are "special", and can't be implemented using regular HVM code. +### On errors. + +Some of the functions may error. `hvm-lang` defines a builtin `Result` ADT which looks like this: + +``` +data Result = (Ok val) | (Err val) +``` + +This allows hvm-lang to do basic error handling. Right now, `val` is meant to be opaque; `val`'s type is not stable and might change from one update to another. + ## `HVM.log` Reads back its first argument and displays it on stdout, then returns its second argument. @@ -52,6 +62,65 @@ This can be very useful, for example, to print strings with newlines in multiple When the first argument is not a string `HVM.print` returns the second argument and no side effects are produced (In other words, it fails silently). +## `HVM.query` + +`HVM.query` reads a line from standard input, and calls its argument with a `Result` that might contain a string containing the user's input. It expects the standard input to be valid utf-8. + +Here's an example program using `HVM.query` +```rs +Join (String.nil) x = x +Join (String.cons head tail) x = (String.cons head (Join tail x)) +main = (((HVM.print "What's your name?") HVM.query) λresult match result { + (Result.ok name): (HVM.print (Join "Hi, " (Join name "!")) *) + (Result.err err): err +}) +``` +This program also shows using the return value of `HVM.print` (which is the identity function) to block `HVM.query` from reducing too early. If we used a naive version of the program, which is this: +```rs +main = (HVM.print "What's your name?" (HVM.query λresult match result { + (Result.ok name): (HVM.print (Join "Hi, " (Join name "!")) *) + (Result.err err): err +})) +``` +We would get asked our name after typing it in. + +## `HVM.store` + +`HVM.store` writes a string to a file, and calls its return value with a `Result` (the `Result`, if it's `Result.ok`, contains `ERA`). + +Example: + +```rs +main = (HVM.store "file.txt" "These are the file contents" λres match res { + (Ok *): "Return value of program" + (Err err): err +}) +``` + +## `HVM.load` + +`HVM.load`, given a filename, reads the file and passes a `Result` that might contain the contents of the file to its argument + +```rs +Join (String.nil) x = x +Join (String.cons head tail) x = (String.cons head (Join tail x)) +main = (HVM.load "file.txt" λres match res { + (Result.ok contents): (HVM.print (Join "File contents: " contents) "Program return value") + (Result.err err): err +}) +``` + +## `HVM.exit` + +`HVM.exit` terminates the processes with the status code determined by its argument. + +```rs +main = (HVM.exit #42) +// Outputs nothing, but `hvml`'s status code will be 42. +``` + +On failure, it terminates the process with a -1 exit status code. + ## `HVM.black_box` `HVM.black_box` is simply the identity function, but it does not get [pre-reduced](compiler-options.md#pre-reduce). This makes it possible to prevent some redexes from getting pre-reduced. diff --git a/src/builtins/exit.rs b/src/builtins/exit.rs new file mode 100644 index 000000000..b783ec277 --- /dev/null +++ b/src/builtins/exit.rs @@ -0,0 +1,72 @@ +use std::sync::{Arc, Mutex}; + +use hvmc::{ + host::Host, + run::{Def, LabSet, Mode, Net, Port, Tag, Wire}, + stdlib::{ArcDef, AsArcDef, HostedDef}, +}; + +use crate::builtins::util::AsDefFunction; + +use super::util::FunctionLikeHosted; + +pub(crate) fn add_exit_def(host: Arc>) { + /// `HVM.exit`. + /// Implements the following reduction rule + /// + /// ```txt + /// ExitDef ~ (a b) + /// --- + /// ExitDefGetStatus ~ a & ~ b + /// ``` + struct ExitDef; + + impl AsDefFunction for ExitDef { + fn call(&self, net: &mut Net, input: Wire, output: Wire) { + // Purposefully deadlock "output" to prevent further reduction + drop(output); + + net.link_wire_port(input, ArcDef::new_arc_port(LabSet::ALL, ExitDefGetStatus)); + } + } + + /// Def that scans for a number, and exits the program with that number as the exit code + /// + /// ```txt + /// ExitDefGetStatus ~ {lab a b} // also works on match and op + /// --- + /// ExitDefGetStatus ~ a & ExitDefGetStatus ~ b + /// + /// ExitDefGetStatus ~ #num + /// --- + /// process::exit(num) + /// + /// ExitDefGetStatus ~ other + /// --- + /// process::exit(-1) + /// ``` + struct ExitDefGetStatus; + impl AsArcDef for ExitDefGetStatus { + fn call(slf: Arc>, net: &mut Net, port: Port) { + match port.tag() { + Tag::Red | Tag::Var | Tag::Num => { + std::process::exit(port.num().try_into().unwrap_or(-1)); + } + Tag::Ref => { + std::process::exit(-1); + } + _ => { + // Commute + let node = port.consume_node(); + net.link_wire_port(node.p1, ArcDef::to_port(slf.clone())); + net.link_wire_port(node.p2, ArcDef::to_port(slf)); + } + } + } + } + + host + .lock() + .unwrap() + .insert_def("HVM.exit", unsafe { HostedDef::new_hosted(LabSet::ALL, FunctionLikeHosted(ExitDef)) }); +} diff --git a/src/builtins/fs.rs b/src/builtins/fs.rs new file mode 100644 index 000000000..5504522ba --- /dev/null +++ b/src/builtins/fs.rs @@ -0,0 +1,171 @@ +use std::sync::{Arc, Mutex}; + +use hvmc::{ + ast, dispatch_dyn_net, + host::Host, + run::{LabSet, Port, Trg, Wire}, + stdlib::{ArcDef, HostedDef}, +}; +use term_to_net::term_to_compat_net; + +use crate::{ + builtins::util::{AsDefFunction, FunctionLike, FunctionLikeHosted}, + net::net_to_hvmc::net_to_hvmc, + readback_hvmc, + term::{ + term_to_net::{self, Labels}, + AdtEncoding, Book, Term, + }, +}; + +#[derive(Clone)] +struct ReadbackData { + book: Arc, + host: Arc>, + labels: Arc, + adt_encoding: AdtEncoding, +} + +const VICIOUS_CIRCLE_MSG: &str = "Found vicious circle"; +const FILENAME_NOT_VALID_MSG: &str = "Filename is not valid string."; +const CONTENTS_NOT_VALID_MSG: &str = "Content is not valid string."; +const FS_ERROR_MSG: &str = "Filesystem error: "; +const INVALID_UTF8_MSG: &str = "UTF-8 error: "; + +/// Adds the filesystem definitions (`HVM.store` and `HVM.load`) to the book +pub(crate) fn add_fs_defs( + book: Arc, + host: Arc>, + labels: Arc, + adt_encoding: AdtEncoding, +) { + #[derive(Clone)] + struct Fs0 { + readback_data: ReadbackData, + save: bool, + } + impl AsDefFunction for Fs0 { + fn call(&self, net: &mut hvmc::run::Net, input: Wire, output: Wire) { + let (wire, port) = net.create_wire(); + let slf = self.clone(); + let readback_node = hvmc::stdlib::readback(slf.readback_data.host.clone(), port, move |net, tree| { + dispatch_dyn_net!(net => { + net.link_wire_port(wire, Port::ERA); + let (term, _errs) = readback_hvmc(&ast::Net { root: tree,redexes: vec![]} , &slf.readback_data.book, &slf.readback_data.labels, false, slf.readback_data.adt_encoding); + let filename = if let Term::Str { ref val } = term { + Some(val.to_string()) + } else { + None + }; + net.link_wire_port(output, ArcDef::new_arc_port(LabSet::ALL, FunctionLike(Fs1 { readback_data: slf.readback_data, save: slf.save, filename }))); + }) + }); + net.link_wire_port(input, readback_node); + } + } + + #[derive(Clone)] + struct Fs1 { + readback_data: ReadbackData, + save: bool, + filename: Option, + } + impl AsDefFunction for Fs1 { + fn call(&self, net: &mut hvmc::run::Net, input: Wire, output: Wire) { + if self.save { + let (wire, port) = net.create_wire(); + let slf = self.clone(); + let mut labels = (*self.readback_data.labels).clone(); + let host = self.readback_data.host.clone(); + let readback_node = hvmc::stdlib::readback( + self.readback_data.host.clone(), + port, + move |net, tree| { + dispatch_dyn_net!(net => { + let (term, _errs) = readback_hvmc(&ast::Net { root: tree,redexes: vec![]} , &slf.readback_data.book, &slf.readback_data.labels, false, slf.readback_data.adt_encoding); + let contents = if let Term::Str { ref val } = term { + Some(val.to_string()) + } else { + None + }; + // Save file + let result = match (slf.filename, contents) { + (None, _) => { + Term::encode_err(Term::encode_str(FILENAME_NOT_VALID_MSG)) + }, + (_, None) => { + Term::encode_err(Term::encode_str(CONTENTS_NOT_VALID_MSG)) + }, + (Some(filename), Some(contents)) => { + match std::fs::write(filename, contents) { + Ok(_) => Term::encode_ok(Term::Era), + Err(e) => Term::encode_err(Term::encode_str(&format!("{FS_ERROR_MSG}{e}"))), + } + }, + }; + let result = term_to_compat_net(&result, &mut labels); + match net_to_hvmc(&result) { + Ok(result) => { + // Return λx (x result) + let app = net.create_node(hvmc::run::Tag::Ctr, 0); + let lam = net.create_node(hvmc::run::Tag::Ctr, 0); + host.lock().unwrap().encode_net(net, Trg::port(app.p1), &result); + net.link_wire_port(wire, Port::ERA); + net.link_port_port(app.p0, lam.p1); + net.link_port_port(app.p2, lam.p2); + + net.link_wire_port(output, lam.p0); + }, + Err(_) => { + // If this happens, we can't even report an error to + // the hvm program, so simply print an error, and plug in an ERA + // The other option would be panicking. + eprintln!("{VICIOUS_CIRCLE_MSG}"); + net.link_wire_port(output, Port::ERA); + }, + } + }) + }, + ); + net.link_wire_port(input, readback_node); + } else { + let app = net.create_node(hvmc::run::Tag::Ctr, 0); + let result = self + .filename + .as_ref() + .ok_or(FILENAME_NOT_VALID_MSG.to_owned()) + .and_then(|filename| std::fs::read(filename).map_err(|e| format!("{FS_ERROR_MSG}{e}"))) + .and_then(|x| { + std::str::from_utf8(&x).map(|x| x.to_string()).map_err(|e| format!("{INVALID_UTF8_MSG}{e}")) + }); + let result = match result { + Ok(s) => Term::encode_ok(Term::encode_str(&s)), + Err(s) => Term::encode_err(Term::encode_str(&s)), + }; + let mut labels = (*self.readback_data.labels).clone(); + let result = term_to_compat_net(&result, &mut labels); + if let Ok(result) = net_to_hvmc(&result) { + self.readback_data.host.lock().unwrap().encode_net(net, Trg::port(app.p1), &result); + } else { + eprintln!("{VICIOUS_CIRCLE_MSG}"); + net.link_port_port(Port::ERA, app.p1); + } + net.link_wire_port(output, app.p2); + net.link_wire_port(input, app.p0); + } + } + } + let readback_data = ReadbackData { book, host: host.clone(), labels, adt_encoding }; + host.lock().unwrap().insert_def("HVM.store", unsafe { + HostedDef::new_hosted( + LabSet::ALL, + FunctionLikeHosted(Fs0 { readback_data: readback_data.clone(), save: true }), + ) + }); + host.lock().unwrap().insert_def("HVM.load", unsafe { + HostedDef::new_hosted( + LabSet::ALL, + FunctionLikeHosted(Fs0 { readback_data: readback_data.clone(), save: false }), + ) + }); +} diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs new file mode 100644 index 000000000..12052e976 --- /dev/null +++ b/src/builtins/mod.rs @@ -0,0 +1,68 @@ +use std::{ + str::FromStr, + sync::{Arc, Mutex}, +}; + +use hvmc::{ast, host::Host, stdlib::LogDef}; + +use crate::{ + readback_hvmc, + term::{ + builtins::{SCONS, SNIL}, + term_to_net::Labels, + AdtEncoding, Book, Term, + }, +}; + +use self::query::make_query_def; + +pub mod exit; +pub mod fs; +pub mod query; +pub mod util; + +/// These are the names of builtin defs that are not in the hvm-lang book, but +/// are present in the hvm-core book. They are implemented using Rust code by +/// [`create_host`] and they can not be rewritten as hvm-lang functions. +pub const CORE_BUILTINS: [&str; 7] = + ["HVM.log", "HVM.black_box", "HVM.print", "HVM.query", "HVM.store", "HVM.load", "HVM.exit"]; +/// List of definition names used by the core builtins +pub const CORE_BUILTINS_USES: [&[&str]; 7] = [&[], &[], &[], &[SCONS, SNIL], &[], &[], &[]]; + +/// Creates a host with the hvm-core primitive definitions built-in. +/// This needs the book as an Arc because the closure that logs +/// data needs access to the book. +pub fn create_host(book: Arc, labels: Arc, adt_encoding: AdtEncoding) -> Arc> { + let host = Arc::new(Mutex::new(Host::default())); + host.lock().unwrap().insert_def("HVM.log", unsafe { + LogDef::new(host.clone(), { + let book = book.clone(); + let labels = labels.clone(); + move |tree| { + let net = hvmc::ast::Net { root: tree, redexes: vec![] }; + let (term, errs) = readback_hvmc(&net, &book, &labels, false, adt_encoding); + println!("{}{}", errs.display_with_severity(crate::diagnostics::Severity::Error), term); + } + }) + }); + host.lock().unwrap().insert_def("HVM.print", unsafe { + LogDef::new(host.clone(), { + let book = book.clone(); + let labels = labels.clone(); + move |tree| { + let net = hvmc::ast::Net { root: tree, redexes: vec![] }; + let (term, _errs) = readback_hvmc(&net, &book, &labels, false, adt_encoding); + if let Term::Str { val } = &term { + println!("{val}"); + } + } + }) + }); + host.lock().unwrap().insert_def("HVM.query", make_query_def(host.clone(), labels.clone())); + fs::add_fs_defs(book.clone(), host.clone(), labels.clone(), adt_encoding); + exit::add_exit_def(host.clone()); + let book = ast::Book::from_str("@HVM.black_box = (x x)").unwrap(); + host.lock().unwrap().insert_book(&book); + + host +} diff --git a/src/builtins/query.rs b/src/builtins/query.rs new file mode 100644 index 000000000..5c2814843 --- /dev/null +++ b/src/builtins/query.rs @@ -0,0 +1,40 @@ +use std::sync::{Arc, Mutex}; + +use hvmc::{ + host::{DefRef, Host}, + run::{LabSet, Port, Trg, Wire}, + stdlib::HostedDef, +}; + +use crate::{ + builtins::util::{AsDefFunction, FunctionLikeHosted}, + term::{term_to_net::Labels, Term}, +}; + +pub(crate) fn make_query_def(host: Arc>, labels: Arc) -> DefRef { + struct Query0 { + host: Arc>, + labels: Arc, + } + impl AsDefFunction for Query0 { + fn call(&self, net: &mut hvmc::run::Net, input: Wire, output: Wire) { + let app_node = net.create_node(hvmc::run::Tag::Ctr, 0); + let mut buf = String::new(); + let _ = std::io::stdin().read_line(&mut buf); + // strip trailing newline + let buf = buf.strip_suffix('\n').unwrap_or(&buf); + let text = Term::encode_ok(Term::encode_str(buf)); + let mut labs = (*self.labels).clone(); + let text = crate::term::term_to_net::term_to_compat_net(&text, &mut labs); + net.link_wire_port(output, app_node.p2); + let Ok(text) = crate::net::net_to_hvmc::net_to_hvmc(&text) else { + net.link_port_port(Port::ERA, app_node.p1); + net.link_wire_port(input, app_node.p0); + return; + }; + self.host.lock().unwrap().encode_net(net, Trg::port(app_node.p1), &text); + net.link_wire_port(input, app_node.p0); + } + } + unsafe { HostedDef::new_hosted(LabSet::ALL, FunctionLikeHosted(Query0 { host, labels })) } +} diff --git a/src/builtins/util.rs b/src/builtins/util.rs new file mode 100644 index 000000000..3578ac6b6 --- /dev/null +++ b/src/builtins/util.rs @@ -0,0 +1,68 @@ +use hvmc::{ + run::{Port, Wire}, + stdlib::{ArcDef, AsArcDef, AsHostedDef}, +}; + +/// This utility struct implements `AsArcDef` +/// It is a wrapper around a type, and makes it act like a function. +/// +/// ```txt +/// FunctionLike[x] ~ (a b) +/// --- +/// ::call(a, b) +/// +/// FunctionLike[x] ~ {lab a b} // (if lab != 0) +/// --- +/// FunctionLike[x] ~ a & FunctionLike[x] ~ b +/// ``` + +pub(crate) struct FunctionLike(pub T); + +pub trait AsDefFunction: Send + Sync + 'static { + fn call(&self, net: &mut hvmc::run::Net, input: Wire, output: Wire); +} + +impl AsArcDef for FunctionLike { + fn call( + slf: std::sync::Arc>, + net: &mut hvmc::run::Net, + port: hvmc::run::Port, + ) { + match port.tag() { + hvmc::run::Tag::Red | hvmc::run::Tag::Var | hvmc::run::Tag::Num | hvmc::run::Tag::Ref => (), + hvmc::run::Tag::Ctr if port.lab() == 0 => { + let node = port.consume_node(); + slf.data.0.call(net, node.p1, node.p2); + } + _ => { + // Commute + let node = port.consume_node(); + net.link_wire_port(node.p1, ArcDef::to_port(slf.clone())); + net.link_wire_port(node.p2, ArcDef::to_port(slf)); + } + } + } +} + +pub(crate) struct FunctionLikeHosted(pub T); + +impl AsHostedDef for FunctionLikeHosted { + fn call( + slf: &hvmc::run::Def, + net: &mut hvmc::run::Net, + port: hvmc::run::Port, + ) { + match port.tag() { + hvmc::run::Tag::Red | hvmc::run::Tag::Var | hvmc::run::Tag::Num | hvmc::run::Tag::Ref => (), + hvmc::run::Tag::Ctr if port.lab() == 0 => { + let node = port.consume_node(); + slf.data.0.call(net, node.p1, node.p2); + } + _ => { + let node = port.consume_node(); + net.link_wire_port(node.p1, Port::new_ref(slf)); + net.link_wire_port(node.p2, Port::new_ref(slf)); + } + } + } +} diff --git a/src/hvmc_net/prune.rs b/src/hvmc_net/prune.rs index 7a1c2f4aa..4057a0acc 100644 --- a/src/hvmc_net/prune.rs +++ b/src/hvmc_net/prune.rs @@ -1,7 +1,7 @@ use hvmc::ast::{Book, Tree}; use std::collections::HashSet; -use crate::CORE_BUILTINS; +use crate::builtins::CORE_BUILTINS; pub fn prune_defs(book: &mut Book, entrypoint: String) { let mut used_defs = HashSet::from_iter(CORE_BUILTINS.iter().map(|x| x.to_string())); diff --git a/src/lib.rs b/src/lib.rs index 1f70acbe6..923e14841 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,23 +1,23 @@ #![feature(box_patterns)] #![feature(let_chains)] +use builtins::create_host; use diagnostics::{DiagnosticOrigin, Diagnostics, DiagnosticsConfig, Severity}; use hvmc::{ ast::{self, Net}, dispatch_dyn_net, host::Host, run::{DynNet, Heap, Rewrites}, - stdlib::LogDef, }; use hvmc_net::{mutual_recursion, prune::prune_defs}; use net::{hvmc_to_net::hvmc_to_net, net_to_hvmc::nets_to_hvmc}; use std::{ - str::FromStr, sync::{Arc, Mutex}, time::Instant, }; use term::{book_to_nets, net_to_term::net_to_term, term_to_net::Labels, AdtEncoding, Book, Ctx, Term}; +pub mod builtins; pub mod diagnostics; pub mod hvmc_net; pub mod net; @@ -28,55 +28,6 @@ pub use term::load_book::load_file_to_book; pub const ENTRY_POINT: &str = "main"; pub const HVM1_ENTRY_POINT: &str = "Main"; -/// These are the names of builtin defs that are not in the hvm-lang book, but -/// are present in the hvm-core book. They are implemented using Rust code by -/// [`create_host`] and they can not be rewritten as hvm-lang functions. -pub const CORE_BUILTINS: [&str; 3] = ["HVM.log", "HVM.black_box", "HVM.print"]; - -/// Creates a host with the hvm-core primitive definitions built-in. -/// This needs the book as an Arc because the closure that logs -/// data needs access to the book. -pub fn create_host(book: Arc, labels: Arc, adt_encoding: AdtEncoding) -> Arc> { - let host = Arc::new(Mutex::new(Host::default())); - host.lock().unwrap().insert_def( - "HVM.log", - hvmc::host::DefRef::Owned(Box::new(LogDef::new({ - let host = host.clone(); - let book = book.clone(); - let labels = labels.clone(); - move |wire| { - let host = host.lock().unwrap(); - let tree = host.readback_tree(&wire); - let net = hvmc::ast::Net { root: tree, redexes: vec![] }; - let (term, errs) = readback_hvmc(&net, &book, &labels, false, adt_encoding); - eprint!("{errs}"); - println!("{term}"); - } - }))), - ); - host.lock().unwrap().insert_def( - "HVM.print", - hvmc::host::DefRef::Owned(Box::new(LogDef::new({ - let host = host.clone(); - let book = book.clone(); - let labels = labels.clone(); - move |wire| { - let host = host.lock().unwrap(); - let tree = host.readback_tree(&wire); - let net = hvmc::ast::Net { root: tree, redexes: vec![] }; - let (term, _errs) = readback_hvmc(&net, &book, &labels, false, adt_encoding); - if let Term::Str { val } = &term { - println!("{val}"); - } - } - }))), - ); - let book = ast::Book::from_str("@HVM.black_box = (x x)").unwrap(); - host.lock().unwrap().insert_book(&book); - - host -} - pub fn check_book(book: &mut Book) -> Result<(), Diagnostics> { // TODO: Do the checks without having to do full compilation let res = compile_book(book, CompileOpts::light(), DiagnosticsConfig::new(Severity::Warning, false), None)?; @@ -96,7 +47,7 @@ pub fn compile_book( let mut core_book = nets_to_hvmc(nets, &mut diagnostics)?; if opts.pre_reduce { - core_book.pre_reduce(&|x| x == book.hvmc_entrypoint(), 1 << 24, 100_000)?; + core_book.pre_reduce(&|x| x == book.hvmc_entrypoint(), 1 << 24, 100_000); } if opts.prune { prune_defs(&mut core_book, book.hvmc_entrypoint().to_string()); diff --git a/src/net/net_to_hvmc.rs b/src/net/net_to_hvmc.rs index dbbdbf609..ebf069d5d 100644 --- a/src/net/net_to_hvmc.rs +++ b/src/net/net_to_hvmc.rs @@ -7,6 +7,7 @@ use crate::{ use hvmc::ast::{Book, Net, Tree}; use std::collections::{HashMap, HashSet}; +#[derive(Debug, Clone)] pub struct ViciousCycleErr; /// Converts the inet-encoded definitions into an hvmc AST Book. diff --git a/src/term/builtins.hvm b/src/term/builtins.hvm index 23ed0a51f..cede389c8 100644 --- a/src/term/builtins.hvm +++ b/src/term/builtins.hvm @@ -1,2 +1,3 @@ data String = (String.cons head tail) | (String.nil) -data List = (List.cons head tail) | (List.nil) \ No newline at end of file +data List = (List.cons head tail) | (List.nil) +data Result = (Result.ok val) | (Result.err val) \ No newline at end of file diff --git a/src/term/builtins.rs b/src/term/builtins.rs index 43766c2d2..dc2729bd3 100644 --- a/src/term/builtins.rs +++ b/src/term/builtins.rs @@ -13,6 +13,10 @@ pub const STRING: &str = "String"; pub const SCONS: &str = "String.cons"; pub const SNIL: &str = "String.nil"; +pub const RESULT: &str = "Result"; +pub const RESULT_OK: &str = "Result.ok"; +pub const RESULT_ERR: &str = "Result.err"; + impl Book { pub fn builtins() -> Book { parse_book(BUILTINS, Book::default, true).expect("Error parsing builtin file, this should not happen") @@ -72,11 +76,18 @@ impl Term { }) } - fn encode_str(val: &str) -> Term { + pub fn encode_str(val: &str) -> Term { val.chars().rfold(Term::r#ref(SNIL), |acc, char| { Term::call(Term::r#ref(SCONS), [Term::Num { val: u64::from(char) }, acc]) }) } + pub fn encode_ok(val: Term) -> Term { + Term::call(Term::r#ref(RESULT_OK), [val]) + } + + pub fn encode_err(val: Term) -> Term { + Term::call(Term::r#ref(RESULT_ERR), [val]) + } } impl Pattern { diff --git a/src/term/term_to_net.rs b/src/term/term_to_net.rs index a72adf0f0..a58294f64 100644 --- a/src/term/term_to_net.rs +++ b/src/term/term_to_net.rs @@ -332,13 +332,13 @@ impl Op { } } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct Labels { pub con: LabelGenerator, pub dup: LabelGenerator, } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct LabelGenerator { pub next: u32, pub name_to_label: HashMap, diff --git a/src/term/transform/definition_pruning.rs b/src/term/transform/definition_pruning.rs index 0f11e5c8d..69897cc64 100644 --- a/src/term/transform/definition_pruning.rs +++ b/src/term/transform/definition_pruning.rs @@ -1,7 +1,7 @@ use crate::{ + builtins::{CORE_BUILTINS, CORE_BUILTINS_USES}, diagnostics::{ToStringVerbose, WarningType}, term::{Adt, AdtEncoding, Book, Ctx, Name, Tag, Term, LIST, STRING}, - CORE_BUILTINS, }; use indexmap::IndexSet; use std::collections::{hash_map::Entry, HashMap}; @@ -134,7 +134,10 @@ impl Book { fn insert_used(&self, def_name: &Name, used: Used, uses: &mut Definitions, adt_encoding: AdtEncoding) { if let Entry::Vacant(e) = uses.entry(def_name.clone()) { e.insert(used); - if CORE_BUILTINS.contains(&def_name.0.as_ref()) { + if let Some(position) = CORE_BUILTINS.iter().position(|x| x == &def_name.0.as_ref()) { + for def_name in CORE_BUILTINS_USES[position] { + self.insert_used(&Name::new(*def_name), used, uses, adt_encoding); + } return; } diff --git a/src/term/transform/resolve_refs.rs b/src/term/transform/resolve_refs.rs index 63b1ece1e..cd939da0c 100644 --- a/src/term/transform/resolve_refs.rs +++ b/src/term/transform/resolve_refs.rs @@ -1,7 +1,7 @@ use crate::{ + builtins::CORE_BUILTINS, diagnostics::{Diagnostics, ToStringVerbose}, term::{Ctx, Name, Pattern, Term}, - CORE_BUILTINS, }; use std::collections::{HashMap, HashSet}; diff --git a/tests/golden_tests/encode_pattern_match/common.hvm b/tests/golden_tests/encode_pattern_match/common.hvm index 2f632d8e3..61ea988b2 100644 --- a/tests/golden_tests/encode_pattern_match/common.hvm +++ b/tests/golden_tests/encode_pattern_match/common.hvm @@ -6,7 +6,7 @@ data Option = (Some x) | None -data Result +data Result_ = (Ok a) | (Err b) diff --git a/tests/golden_tests/encode_pattern_match/match_num_adt_tup_parser.hvm b/tests/golden_tests/encode_pattern_match/match_num_adt_tup_parser.hvm index 6db64f927..6ec488ed8 100644 --- a/tests/golden_tests/encode_pattern_match/match_num_adt_tup_parser.hvm +++ b/tests/golden_tests/encode_pattern_match/match_num_adt_tup_parser.hvm @@ -1,5 +1,5 @@ // Testing various forms of pattern matching -data Result = (Ok val) | (Err err) +data Result_ = (Ok val) | (Err err) Parse state (String.cons '(' xs) = (Ok ('(', xs, state)) Parse state (String.cons ')' xs) = (Ok (')', xs, state)) diff --git a/tests/golden_tests/run_file/match_num_adt_tup_parser.hvm b/tests/golden_tests/run_file/match_num_adt_tup_parser.hvm index 6db64f927..6ec488ed8 100644 --- a/tests/golden_tests/run_file/match_num_adt_tup_parser.hvm +++ b/tests/golden_tests/run_file/match_num_adt_tup_parser.hvm @@ -1,5 +1,5 @@ // Testing various forms of pattern matching -data Result = (Ok val) | (Err err) +data Result_ = (Ok val) | (Err err) Parse state (String.cons '(' xs) = (Ok ('(', xs, state)) Parse state (String.cons ')' xs) = (Ok (')', xs, state)) diff --git a/tests/snapshots/compile_file_o_all__list_merge_sort.hvm.snap b/tests/snapshots/compile_file_o_all__list_merge_sort.hvm.snap index 7b4b25121..1887b6f36 100644 --- a/tests/snapshots/compile_file_o_all__list_merge_sort.hvm.snap +++ b/tests/snapshots/compile_file_o_all__list_merge_sort.hvm.snap @@ -7,18 +7,18 @@ input_file: tests/golden_tests/compile_file_o_all/list_merge_sort.hvm @If$C1 = (* (a a)) @Map = ({4 @Map$C0 {4 @Unpack$C3_$_MergePair$C4_$_Map$C1 a}} a) @Map$C0 = {4 a {4 {4 @Map$C0 {4 @Unpack$C3_$_MergePair$C4_$_Map$C1 (b c)}} ({5 (a d) b} {4 {4 d {4 c e}} {4 * e}})}} -@Merge$C0 = {4 {7 a {7 b c}} {4 {9 d {4 @Merge$C0 {4 @Merge$C1 (e (f (g h)))}}} ({11 (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))}})) {11 o e}} ({13 i {13 j f}} ({15 {4 @Merge$C2 {4 @Merge$C3 (o ({4 {4 b {4 d p}} {4 * p}} k))}} g} n)))}} +@Merge$C0 = {4 {9 a {9 b c}} {4 {11 d {4 @Merge$C0 {4 @Merge$C1 (e (f (g h)))}}} ({13 (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))}})) {13 o e}} ({15 i {15 j f}} ({17 {4 @Merge$C2 {4 @Merge$C3 (o ({4 {4 b {4 d p}} {4 * p}} k))}} g} n)))}} @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 @Unpack$C3_$_MergePair$C4_$_Map$C1 (b c)}} ({17 d b} ({4 @Merge$C2 {4 @Merge$C3 (d (a e))}} {4 {4 e {4 c f}} {4 * f}}))}} +@MergePair$C0 = {4 a {4 {4 @MergePair$C3 {4 @Unpack$C3_$_MergePair$C4_$_Map$C1 (b c)}} ({7 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)}} @Nil = {4 * {4 a a}} @Pure = (a {4 {4 a {4 @Nil b}} {4 * b}}) @Unpack = (a ({4 @Unpack$C2 {4 @Unpack$C3_$_MergePair$C4_$_Map$C1 (a b)}} b)) -@Unpack$C0 = {4 a {4 {4 @MergePair$C3 {4 @Unpack$C3_$_MergePair$C4_$_Map$C1 (b {4 @Unpack$C0 {4 @Unpack$C1 (c (d e))}})}} ({3 c {17 f b}} ({4 @Merge$C2 {4 @Merge$C3 (f (a d))}} e))}} +@Unpack$C0 = {4 a {4 {4 @MergePair$C3 {4 @Unpack$C3_$_MergePair$C4_$_Map$C1 (b {4 @Unpack$C0 {4 @Unpack$C1 (c (d e))}})}} ({3 c {7 f b}} ({4 @Merge$C2 {4 @Merge$C3 (f (a d))}} e))}} @Unpack$C1 = (* (a a)) @Unpack$C2 = {4 a {4 {4 @Unpack$C0 {4 @Unpack$C1 (b (a c))}} (b c)}} @Unpack$C3_$_MergePair$C4_$_Map$C1 = (* @Nil) diff --git a/tests/snapshots/encode_pattern_match__common.hvm.snap b/tests/snapshots/encode_pattern_match__common.hvm.snap index 8ebc46207..3a378f4b4 100644 --- a/tests/snapshots/encode_pattern_match__common.hvm.snap +++ b/tests/snapshots/encode_pattern_match__common.hvm.snap @@ -11,6 +11,10 @@ TaggedScott: (North) = #Direction λa #Direction λ* #Direction λ* #Direction λ* a +(Green) = #Light λ* #Light λ* #Light λc c + +(Yellow) = #Light λ* #Light λb #Light λ* b + (Filled) = λa #Box λb #Box λ* #Box (b a) (Empty) = #Box λ* #Box λb b @@ -19,9 +23,9 @@ TaggedScott: (None) = #Option λ* #Option λb b -(Ok) = λa #Result λb #Result λ* #Result (b a) +(Ok) = λa #Result_ λb #Result_ λ* #Result_ (b a) -(Err) = λa #Result λ* #Result λc #Result (c a) +(Err) = λa #Result_ λ* #Result_ λc #Result_ (c a) (Cons) = λa λb #List_ λc #List_ λ* #List_ (c a b) @@ -33,10 +37,6 @@ TaggedScott: (Red) = #Light λa #Light λ* #Light λ* a -(Yellow) = #Light λ* #Light λb #Light λ* b - -(Green) = #Light λ* #Light λ* #Light λc c - Scott: (West) = λ* λ* λ* λd d @@ -46,6 +46,10 @@ Scott: (North) = λa λ* λ* λ* a +(Green) = λ* λ* λc c + +(Yellow) = λ* λb λ* b + (Filled) = λa λb λ* (b a) (Empty) = λ* λb b @@ -67,7 +71,3 @@ Scott: (False) = λ* λb b (Red) = λa λ* λ* a - -(Yellow) = λ* λb λ* b - -(Green) = λ* λ* λc c diff --git a/tests/snapshots/encode_pattern_match__expr.hvm.snap b/tests/snapshots/encode_pattern_match__expr.hvm.snap index 144e94156..3baca1515 100644 --- a/tests/snapshots/encode_pattern_match__expr.hvm.snap +++ b/tests/snapshots/encode_pattern_match__expr.hvm.snap @@ -11,6 +11,10 @@ TaggedScott: (Add) = #Op λa #Op λ* #Op λ* #Op λ* a +(Op2) = λa λb λc #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λl #Expr (l a b c) + +(Tup) = λa λb #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λj #Expr λ* #Expr (j a b) + (Var) = λa #Expr λb #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr (b a) (Num) = λa #Expr λ* #Expr λc #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr (c a) @@ -25,10 +29,6 @@ TaggedScott: (Dup) = λa λb λc λd #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λk #Expr λ* #Expr λ* #Expr (k a b c d) -(Tup) = λa λb #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λj #Expr λ* #Expr (j a b) - -(Op2) = λa λb λc #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λ* #Expr λl #Expr (l a b c) - Scott: (Div) = λ* λ* λ* λd d @@ -38,6 +38,10 @@ Scott: (Add) = λa λ* λ* λ* a +(Op2) = λa λb λc λ* λ* λ* λ* λ* λ* λ* λ* λl (l a b c) + +(Tup) = λa λb λ* λ* λ* λ* λ* λ* λ* λj λ* (j a b) + (Var) = λa λb λ* λ* λ* λ* λ* λ* λ* λ* (b a) (Num) = λa λ* λc λ* λ* λ* λ* λ* λ* λ* (c a) @@ -51,7 +55,3 @@ Scott: (Let) = λa λb λc λ* λ* λ* λ* λ* λi λ* λ* λ* (i a b c) (Dup) = λa λb λc λd λ* λ* λ* λ* λ* λ* λk λ* λ* (k a b c d) - -(Tup) = λa λb λ* λ* λ* λ* λ* λ* λ* λj λ* (j a b) - -(Op2) = λa λb λc λ* λ* λ* λ* λ* λ* λ* λ* λl (l a b c) diff --git a/tests/snapshots/encode_pattern_match__match_list_sugar.hvm.snap b/tests/snapshots/encode_pattern_match__match_list_sugar.hvm.snap index abb91d15f..72f9969f9 100644 --- a/tests/snapshots/encode_pattern_match__match_list_sugar.hvm.snap +++ b/tests/snapshots/encode_pattern_match__match_list_sugar.hvm.snap @@ -5,13 +5,13 @@ input_file: tests/golden_tests/encode_pattern_match/match_list_sugar.hvm TaggedScott: (main) = #List (List.nil #List λ* #List λ* 1 0) -(List.nil) = #List λ* #List λb b - (List.cons) = λa λb #List λc #List λ* #List (c a b) +(List.nil) = #List λ* #List λb b + Scott: (main) = (List.nil λ* λ* 1 0) -(List.nil) = λ* λb b - (List.cons) = λa λb λc λ* (c a b) + +(List.nil) = λ* λb b diff --git a/tests/snapshots/encode_pattern_match__match_num_adt_tup_parser.hvm.snap b/tests/snapshots/encode_pattern_match__match_num_adt_tup_parser.hvm.snap index 66659352b..fff85613a 100644 --- a/tests/snapshots/encode_pattern_match__match_num_adt_tup_parser.hvm.snap +++ b/tests/snapshots/encode_pattern_match__match_num_adt_tup_parser.hvm.snap @@ -5,15 +5,15 @@ input_file: tests/golden_tests/encode_pattern_match/match_num_adt_tup_parser.hvm TaggedScott: (Parse) = λa λb (#String (b #String λc #String λd λe (match (- c 40) { 0: λf λg (Ok (40, g, f)); 1+: λi match i { 0: λj λk (Ok (41, k, j)); 1+: λm match (- m 18446744073709551584) { 0: λn λo (Ok (0, o, n)); 1+: λq λs λt (Err ((String.cons (+ q 11) t), s)) } } } e d) λu (Err (String.nil, u))) a) -(main) = #Result ((Parse * (String.cons 40 (String.cons 43 String.nil))) #Result λd let (e, f, g) = d; (e, (Parse g f)) #Result λh (Err h)) +(main) = #Result_ ((Parse * (String.cons 40 (String.cons 43 String.nil))) #Result_ λd let (e, f, g) = d; (e, (Parse g f)) #Result_ λh (Err h)) (String.cons) = λa λb #String λc #String λ* #String (c a b) (String.nil) = #String λ* #String λb b -(Err) = λa #Result λ* #Result λc #Result (c a) +(Err) = λa #Result_ λ* #Result_ λc #Result_ (c a) -(Ok) = λa #Result λb #Result λ* #Result (b a) +(Ok) = λa #Result_ λb #Result_ λ* #Result_ (b a) Scott: (Parse) = λa λb (b λc λd λe (match (- c 40) { 0: λf λg (Ok (40, g, f)); 1+: λi match i { 0: λj λk (Ok (41, k, j)); 1+: λm match (- m 18446744073709551584) { 0: λn λo (Ok (0, o, n)); 1+: λq λs λt (Err ((String.cons (+ q 11) t), s)) } } } e d) λu (Err (String.nil, u)) a) diff --git a/tests/snapshots/encode_pattern_match__weekday.hvm.snap b/tests/snapshots/encode_pattern_match__weekday.hvm.snap index e3f10c74d..2e6136bd7 100644 --- a/tests/snapshots/encode_pattern_match__weekday.hvm.snap +++ b/tests/snapshots/encode_pattern_match__weekday.hvm.snap @@ -13,11 +13,11 @@ TaggedScott: (Thursday) = #Weekday λ* #Weekday λ* #Weekday λ* #Weekday λd #Weekday λ* #Weekday λ* #Weekday λ* d -(Monday) = #Weekday λa #Weekday λ* #Weekday λ* #Weekday λ* #Weekday λ* #Weekday λ* #Weekday λ* a +(Wednesday) = #Weekday λ* #Weekday λ* #Weekday λc #Weekday λ* #Weekday λ* #Weekday λ* #Weekday λ* c (Tuesday) = #Weekday λ* #Weekday λb #Weekday λ* #Weekday λ* #Weekday λ* #Weekday λ* #Weekday λ* b -(Wednesday) = #Weekday λ* #Weekday λ* #Weekday λc #Weekday λ* #Weekday λ* #Weekday λ* #Weekday λ* c +(Monday) = #Weekday λa #Weekday λ* #Weekday λ* #Weekday λ* #Weekday λ* #Weekday λ* #Weekday λ* a Scott: (main) = (λa a Saturday) @@ -30,10 +30,8 @@ Scott: (Thursday) = λ* λ* λ* λd λ* λ* λ* d -(Monday) = λa λ* λ* λ* λ* λ* λ* a - -(Tuesday) = λ* λb λ* λ* λ* λ* λ* b - (Wednesday) = λ* λ* λc λ* λ* λ* λ* c +(Tuesday) = λ* λb λ* λ* λ* λ* λ* b +(Monday) = λa λ* λ* λ* λ* λ* λ* a diff --git a/tests/snapshots/simplify_matches__already_flat.hvm.snap b/tests/snapshots/simplify_matches__already_flat.hvm.snap index 46a8bf659..3a246c9c8 100644 --- a/tests/snapshots/simplify_matches__already_flat.hvm.snap +++ b/tests/snapshots/simplify_matches__already_flat.hvm.snap @@ -22,12 +22,12 @@ input_file: tests/golden_tests/simplify_matches/already_flat.hvm (CtrB0) = #Baz λa #Baz λ* #Baz λ* #Baz λ* a +(CtrA3) = λa #Bar λ* #Bar λ* #Bar λd #Bar (d a) + +(CtrA2) = λa λb #Bar λ* #Bar λd #Bar λ* #Bar (d a b) + (CtrA) = #Foo λa #Foo λ* a (CtrB) = λa #Foo λ* #Foo λc #Foo (c a) (CtrA1) = λa #Bar λb #Bar λ* #Bar λ* #Bar (b a) - -(CtrA2) = λa λb #Bar λ* #Bar λd #Bar λ* #Bar (d a b) - -(CtrA3) = λa #Bar λ* #Bar λ* #Bar λd #Bar (d a) diff --git a/tests/snapshots/simplify_matches__nested2.hvm.snap b/tests/snapshots/simplify_matches__nested2.hvm.snap index 3ca3b9d15..73c350731 100644 --- a/tests/snapshots/simplify_matches__nested2.hvm.snap +++ b/tests/snapshots/simplify_matches__nested2.hvm.snap @@ -4,6 +4,6 @@ input_file: tests/golden_tests/simplify_matches/nested2.hvm --- (Foo) = λa λb (match b { (List.cons c d): λe (match d { (List.cons f g): λh λi (h i f g); (List.nil): λj λk (j (List.cons k List.nil)) } e c); (List.nil): λm (m List.nil) } a) -(List.nil) = #List λ* #List λb b - (List.cons) = λa λb #List λc #List λ* #List (c a b) + +(List.nil) = #List λ* #List λb b