Merge pull request #501 from HigherOrderCO/475-use-the-hvm-ast-for-representing-inets-inside-the-compiler

Use the hvm ast for representing inets inside the compiler
This commit is contained in:
Nicolas Abril 2024-05-25 18:21:33 +00:00 committed by GitHub
commit 948eaf0a09
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 669 additions and 1653 deletions

57
Cargo.lock generated
View File

@ -60,20 +60,14 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "arrayvec"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "bend-lang"
version = "0.2.20"
version = "0.2.21"
dependencies = [
"TSPL",
"arrayvec",
"clap",
"highlight_error",
"hvm",
"indexmap",
"insta",
"interner",
@ -86,9 +80,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.97"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4"
checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f"
[[package]]
name = "cfg-if"
@ -184,12 +178,31 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "highlight_error"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e18805660d7b6b2e2b9f316a5099521b5998d5cba4dda11b5157a21aaef03"
[[package]]
name = "hvm"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32cf1c2a5333c940ab1c5b6c64e0f3242739332446b572b07a87f46753c9f69e"
dependencies = [
"TSPL",
"cc",
"clap",
"highlight_error",
"num_cpus",
]
[[package]]
name = "indexmap"
version = "2.2.6"
@ -241,9 +254,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.153"
version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "linked-hash-map"
@ -258,10 +271,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f16fbe026127b8ff026fcc7419ddf201d47807295690de255bb68fced2ba15af"
[[package]]
name = "proc-macro2"
version = "1.0.82"
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "proc-macro2"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43"
dependencies = [
"unicode-ident",
]
@ -326,9 +349,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.64"
version = "2.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ad3dee41f36859875573074334c200d1add8e4a87bb37113ebd31d926b7b11f"
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
dependencies = [
"proc-macro2",
"quote",

View File

@ -2,7 +2,7 @@
name = "bend-lang"
description = "A high-level, massively parallel programming language"
license = "Apache-2.0"
version = "0.2.20"
version = "0.2.21"
edition = "2021"
exclude = ["tests/snapshots/"]
@ -24,9 +24,9 @@ cli = ["dep:clap"]
[dependencies]
TSPL = "0.0.12"
arrayvec = "0.7.4"
clap = { version = "4.4.1", features = ["derive"], optional = true }
highlight_error = "0.1.1"
hvm = "2.0.16"
indexmap = "2.2.3"
interner = "0.2.1"
itertools = "0.11.0"

View File

@ -83,6 +83,7 @@
"Pythonish",
"quadtree",
"quadtrees",
"rbag",
"readback",
"recursively",
"redex",

View File

@ -59,7 +59,7 @@ If you have a set of mutually recursive functions, you only need to make one of
### Automatic optimization
HVM-lang carries out [match linearization](compiler-options.md#linearize-matches) and [combinator floating](compiler-options.md#float-combinators) optimizations, enabled through the CLI, which are active by default in strict mode.
Bend carries out [match linearization](compiler-options.md#linearize-matches) and [combinator floating](compiler-options.md#float-combinators) optimizations, enabled through the CLI, which are active by default in strict mode.
Consider the code below:

View File

@ -94,7 +94,7 @@ Operation | Description | Accepted types | Return type
### Pattern matching
HVM-lang also includes a `switch` syntax for pattern-matching U24 numbers.
Bend also includes a `switch` syntax for pattern-matching U24 numbers.
```rs
Number.to_church = λn λf λx

View File

@ -47,7 +47,7 @@ UnwrapOrZero x = (x λx.val x.val 0)
### Pattern Matching functions
Besides `match`and `switch` terms, hvm-lang also supports equational-style pattern matching functions.
Besides `match`and `switch` terms, Bend also supports equational-style pattern matching functions.
```py
And True b = b

View File

@ -71,7 +71,7 @@ def radix(n):
r = swap(n & 512, r, Map_/Free)
return radix2(n, r)
# At the moment, we need to manually break very large functiions into smaller ones
# At the moment, we need to manually break very large functions into smaller ones
# if we want to run this program on the GPU.
# In a future version of Bend, we will be able to do this automatically.
def radix2(n, r):

View File

@ -1,7 +1,6 @@
use crate::{
diagnostics::{Diagnostics, DiagnosticsConfig},
hvm::{self, ast::get_typ},
maybe_grow, ENTRY_POINT,
maybe_grow, multi_iterator, ENTRY_POINT,
};
// use hvmc::ast::get_typ;
use indexmap::{IndexMap, IndexSet};
@ -19,7 +18,7 @@ pub mod term_to_net;
pub mod transform;
pub use net_to_term::{net_to_term, ReadbackError};
pub use term_to_net::{book_to_nets, term_to_net};
pub use term_to_net::{book_to_hvm, term_to_hvm};
pub static STRINGS: GlobalPool<String> = GlobalPool::new();
#[derive(Debug)]
@ -247,45 +246,6 @@ pub struct Name(GlobalString);
/* Implementations */
/// 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)]
enum $Iter<$($Variant),*> {
$($Variant { iter: $Variant }),*
}
impl<$($Variant),*> $Iter<$($Variant),*> {
$(
#[allow(non_snake_case)]
fn $Variant(iter: impl IntoIterator<IntoIter = $Variant>) -> Self {
$Iter::$Variant { iter: iter.into_iter() }
}
)*
}
impl<T, $($Variant: Iterator<Item = T>),*> Iterator for $Iter<$($Variant),*> {
type Item = T;
fn next(&mut self) -> Option<T> {
match self { $($Iter::$Variant { iter } => iter.next()),* }
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self { $($Iter::$Variant { iter } => iter.size_hint()),* }
}
}
impl<T, $($Variant: DoubleEndedIterator<Item = T>),*> DoubleEndedIterator for $Iter<$($Variant),*> {
fn next_back(&mut self) -> Option<T> {
match self { $($Iter::$Variant { iter } => iter.next_back()),* }
}
}
};
}
impl PartialEq<str> for Name {
fn eq(&self, other: &str) -> bool {
&**self == other
@ -901,17 +861,17 @@ impl Num {
pub fn to_bits(&self) -> u32 {
match self {
Num::U24(val) => hvm::ast::new_u24(*val),
Num::I24(val) => hvm::ast::new_i24(*val),
Num::F24(val) => hvm::ast::new_f24(*val),
Num::U24(val) => hvm::hvm::Numb::new_u24(*val).0,
Num::I24(val) => hvm::hvm::Numb::new_i24(*val).0,
Num::F24(val) => hvm::hvm::Numb::new_f24(*val).0,
}
}
pub fn from_bits(bits: u32) -> Self {
match get_typ(bits) {
hvm::ast::TY_U24 => Num::U24(hvm::ast::get_u24(bits)),
hvm::ast::TY_I24 => Num::I24(hvm::ast::get_i24(bits)),
hvm::ast::TY_F24 => Num::F24(hvm::ast::get_f24(bits)),
match hvm::hvm::Numb::get_typ(&hvm::hvm::Numb(bits)) {
hvm::hvm::TY_U24 => Num::U24(hvm::hvm::Numb::get_u24(&hvm::hvm::Numb(bits))),
hvm::hvm::TY_I24 => Num::I24(hvm::hvm::Numb::get_i24(&hvm::hvm::Numb(bits))),
hvm::hvm::TY_F24 => Num::F24(hvm::hvm::Numb::get_f24(&hvm::hvm::Numb(bits))),
_ => unreachable!("Invalid Num bits"),
}
}

View File

@ -1,15 +1,12 @@
use hvm::ast::{get_f24, get_i24, get_typ, get_u24};
use crate::{
diagnostics::{DiagnosticOrigin, Diagnostics, Severity},
fun::{term_to_net::Labels, Book, FanKind, Name, Op, Pattern, Tag, Term},
hvm, maybe_grow,
fun::{term_to_net::Labels, Book, FanKind, Name, Num, Op, Pattern, Tag, Term},
maybe_grow,
net::{CtrKind, INet, NodeId, NodeKind, Port, SlotId, ROOT},
};
use hvm::hvm::Numb;
use std::collections::{BTreeSet, HashMap, HashSet};
use super::Num;
/// Converts an Interaction-INet to a Lambda Calculus term
pub fn net_to_term(
net: &INet,
@ -224,11 +221,12 @@ impl Reader<'_> {
let opr_node = self.net.enter_port(Port(port0_node, 0)).node();
let opr_kind = self.net.node(opr_node).kind.clone();
let opr = if let NodeKind::Num { val } = opr_kind {
if get_typ(val) != hvm::ast::TY_SYM {
let typ = hvm::hvm::Numb::get_typ(&Numb(val));
if typ != hvm::hvm::TY_SYM {
self.error(ReadbackError::InvalidNumericOp);
return Term::Err;
}
if let Some(op) = Op::from_native_tag(val, NumType::U24) {
if let Some(op) = Op::from_native_tag(typ, NumType::U24) {
op
} else {
self.error(ReadbackError::InvalidNumericOp);
@ -378,32 +376,32 @@ enum NumType {
}
impl Op {
fn from_native_tag(val: u32, typ: NumType) -> Option<Op> {
fn from_native_tag(val: hvm::hvm::Tag, typ: NumType) -> Option<Op> {
let op = match val {
0x4 => Op::ADD,
0x5 => Op::SUB,
0x6 => Op::MUL,
0x7 => Op::DIV,
0x8 => Op::REM,
0x9 => Op::EQ,
0xa => Op::NEQ,
0xb => Op::LT,
0xc => Op::GT,
0xd => {
hvm::hvm::OP_ADD => Op::ADD,
hvm::hvm::OP_SUB => Op::SUB,
hvm::hvm::OP_MUL => Op::MUL,
hvm::hvm::OP_DIV => Op::DIV,
hvm::hvm::OP_REM => Op::REM,
hvm::hvm::OP_EQ => Op::EQ,
hvm::hvm::OP_NEQ => Op::NEQ,
hvm::hvm::OP_LT => Op::LT,
hvm::hvm::OP_GT => Op::GT,
hvm::hvm::OP_AND => {
if typ == NumType::F24 {
Op::ATN
} else {
Op::AND
}
}
0xe => {
hvm::hvm::OP_OR => {
if typ == NumType::F24 {
Op::LOG
} else {
Op::OR
}
}
0xf => {
hvm::hvm::OP_XOR => {
if typ == NumType::F24 {
Op::POW
} else {
@ -477,12 +475,12 @@ impl Term {
}
fn num_from_bits_with_type(val: u32, typ: u32) -> Term {
match get_typ(typ) {
match hvm::hvm::Numb::get_typ(&Numb(typ)) {
// No type information, assume u24 by default
hvm::ast::TY_SYM => Term::Num { val: Num::U24(get_u24(val)) },
hvm::ast::TY_U24 => Term::Num { val: Num::U24(get_u24(val)) },
hvm::ast::TY_I24 => Term::Num { val: Num::I24(get_i24(val)) },
hvm::ast::TY_F24 => Term::Num { val: Num::F24(get_f24(val)) },
hvm::hvm::TY_SYM => Term::Num { val: Num::U24(Numb::get_u24(&Numb(val))) },
hvm::hvm::TY_U24 => Term::Num { val: Num::U24(Numb::get_u24(&Numb(val))) },
hvm::hvm::TY_I24 => Term::Num { val: Num::I24(Numb::get_i24(&Numb(val))) },
hvm::hvm::TY_F24 => Term::Num { val: Num::F24(Numb::get_f24(&Numb(val))) },
_ => Term::Err,
}
}

View File

@ -1,39 +1,37 @@
use crate::{
diagnostics::Diagnostics,
fun::{Book, Name, Pattern, Tag, Term},
hvm, maybe_grow,
fun::{num_to_name, Book, FanKind, Name, Op, Pattern, Term},
hvm::{net_trees, tree_children},
maybe_grow,
net::CtrKind::{self, *},
};
use hvm::ast::{Net, Tree};
use loaned::LoanedMut;
use std::{
collections::{hash_map::Entry, HashMap},
ops::{Index, IndexMut},
};
use hvm::ast::{Net, Tree};
use loaned::LoanedMut;
use super::{num_to_name, FanKind, Op};
#[derive(Debug, Clone)]
pub struct ViciousCycleErr;
pub fn book_to_nets(book: &Book, diags: &mut Diagnostics) -> Result<(hvm::ast::Book, Labels), Diagnostics> {
pub fn book_to_hvm(book: &Book, diags: &mut Diagnostics) -> Result<(hvm::ast::Book, Labels), Diagnostics> {
diags.start_pass();
let mut hvmc = hvm::ast::Book::default();
let mut hvm_book = hvm::ast::Book { defs: Default::default() };
let mut labels = Labels::default();
let main = book.entrypoint.as_ref().unwrap();
for def in book.defs.values() {
for rule in def.rules.iter() {
let net = term_to_net(&rule.body, &mut labels);
let net = term_to_hvm(&rule.body, &mut labels);
let name = if def.name == *main { book.hvmc_entrypoint().to_string() } else { def.name.0.to_string() };
match net {
Ok(net) => {
hvmc.insert(name, net);
hvm_book.defs.insert(name, net);
}
Err(err) => diags.add_inet_error(err, name),
}
@ -43,12 +41,12 @@ pub fn book_to_nets(book: &Book, diags: &mut Diagnostics) -> Result<(hvm::ast::B
labels.con.finish();
labels.dup.finish();
diags.fatal((hvmc, labels))
diags.fatal((hvm_book, labels))
}
/// Converts an LC term into an IC net.
pub fn term_to_net(term: &Term, labels: &mut Labels) -> Result<Net, String> {
let mut net = Net::default();
pub fn term_to_hvm(term: &Term, labels: &mut Labels) -> Result<Net, String> {
let mut net = Net { root: Tree::Era, rbag: Default::default() };
let mut state = EncodeTermState {
lets: Default::default(),
@ -61,11 +59,11 @@ pub fn term_to_net(term: &Term, labels: &mut Labels) -> Result<Net, String> {
};
state.encode_term(term, Place::Hole(&mut net.root));
LoanedMut::from(std::mem::take(&mut state.redexes)).place(&mut net.redexes);
LoanedMut::from(std::mem::take(&mut state.redexes)).place(&mut net.rbag);
let EncodeTermState { created_nodes, .. } = { state };
let found_nodes = net.trees().map(count_nodes).sum::<usize>();
let found_nodes = net_trees(&net).map(count_nodes).sum::<usize>();
if created_nodes != found_nodes {
return Err("Found term that compiles into an inet with a vicious cycle".into());
}
@ -86,7 +84,7 @@ struct EncodeTermState<'t, 'l> {
fn count_nodes(tree: &Tree) -> usize {
maybe_grow(|| {
usize::from(tree.children().next().is_some()) + tree.children().map(count_nodes).sum::<usize>()
usize::from(tree_children(tree).next().is_some()) + tree_children(tree).map(count_nodes).sum::<usize>()
})
}
@ -111,7 +109,7 @@ impl<'t, 'l> EncodeTermState<'t, 'l> {
Term::Link { nam } => self.link_var(true, nam, up),
Term::Ref { nam } => self.link(up, Place::Tree(LoanedMut::new(Tree::Ref { nam: nam.to_string() }))),
Term::Num { val } => {
let val = val.to_bits();
let val = hvm::ast::Numb(val.to_bits());
self.link(up, Place::Tree(LoanedMut::new(Tree::Num { val })))
}
// A lambda becomes to a con node. Ports:
@ -144,10 +142,12 @@ impl<'t, 'l> EncodeTermState<'t, 'l> {
assert!(with.is_empty());
assert!(rules.len() == 2);
self.created_nodes += 1;
self.created_nodes += 2;
let loaned = Tree::Swi { fst: Box::new(Tree::Con{fst: Box::new(Tree::Era), snd: Box::new(Tree::Era)}), snd: Box::new(Tree::Era)};
let ((zero, succ, out), node) =
LoanedMut::loan_with(Tree::Mat { zero: hole(), succ: hole(), out: hole() }, |t, l| {
let Tree::Mat { zero, succ, out, .. } = t else { unreachable!() };
LoanedMut::loan_with(loaned, |t, l| {
let Tree::Swi { fst, snd: out } = t else { unreachable!() };
let Tree::Con { fst:zero, snd: succ } = fst.as_mut() else { unreachable!() };
(l.loan_mut(zero), l.loan_mut(succ), l.loan_mut(out))
});
@ -172,7 +172,7 @@ impl<'t, 'l> EncodeTermState<'t, 'l> {
// Partially apply with fst
(Term::Num { val }, snd) => {
let val = val.to_bits();
let val = (val & !0x1F) | opr.to_native_tag();
let val = hvm::ast::Numb((val & !0x1F) | opr.to_native_tag() as u32);
let fst = Place::Tree(LoanedMut::new(Tree::Num { val }));
let node = self.new_opr();
self.link(fst, node.0);
@ -183,7 +183,7 @@ impl<'t, 'l> EncodeTermState<'t, 'l> {
(fst, Term::Num { val }) => {
if [Op::POW, Op::ATN, Op::LOG].contains(opr) {
// POW, ATN and LOG share tags with AND, OR and XOR, so don't flip or results will be wrong
let opr_val = hvm::ast::new_sym(opr.to_native_tag());
let opr_val = hvm::ast::Numb(hvm::hvm::Numb::new_sym(opr.to_native_tag()).0);
let oper = Place::Tree(LoanedMut::new(Tree::Num { val: opr_val }));
let node1 = self.new_opr();
self.encode_term(fst, node1.0);
@ -195,7 +195,7 @@ impl<'t, 'l> EncodeTermState<'t, 'l> {
} else {
// flip
let val = val.to_bits();
let val = (val & !0x1F) | hvm::ast::flip_sym(opr.to_native_tag());
let val = hvm::ast::Numb((val & !0x1F) | flip_sym(opr.to_native_tag()) as u32);
let snd = Place::Tree(LoanedMut::new(Tree::Num { val }));
let node = self.new_opr();
self.encode_term(fst, node.0);
@ -205,7 +205,7 @@ impl<'t, 'l> EncodeTermState<'t, 'l> {
}
// Don't partially apply
(fst, snd) => {
let opr_val = hvm::ast::new_sym(opr.to_native_tag());
let opr_val = hvm::ast::Numb(hvm::hvm::Numb::new_sym(opr.to_native_tag()).0);
let oper = Place::Tree(LoanedMut::new(Tree::Num { val: opr_val }));
let node1 = self.new_opr();
self.encode_term(fst, node1.0);
@ -252,10 +252,12 @@ impl<'t, 'l> EncodeTermState<'t, 'l> {
fn link(&mut self, a: Place<'t>, b: Place<'t>) {
match (a, b) {
(Place::Tree(a), Place::Tree(b)) => self.redexes.push(LoanedMut::merge(Default::default(), |r, m| {
m.place(b, &mut r.1);
m.place(a, &mut r.2);
})),
(Place::Tree(a), Place::Tree(b)) => {
self.redexes.push(LoanedMut::merge((false, Tree::Era, Tree::Era), |r, m| {
m.place(b, &mut r.1);
m.place(a, &mut r.2);
}))
}
(Place::Tree(t), Place::Hole(h)) | (Place::Hole(h), Place::Tree(t)) => {
t.place(h);
}
@ -277,20 +279,25 @@ impl<'t, 'l> EncodeTermState<'t, 'l> {
fn new_ctr(&mut self, kind: CtrKind) -> (Place<'t>, Place<'t>, Place<'t>) {
self.created_nodes += 1;
let (ports, node) =
LoanedMut::loan_with(Tree::Ctr { lab: kind.to_lab(), ports: vec![Tree::Era, Tree::Era] }, |t, l| {
let Tree::Ctr { ports, .. } = t else { unreachable!() };
l.loan_mut(ports)
});
let (a, b) = ports.split_at_mut(1);
(Place::Tree(node), Place::Hole(&mut a[0]), Place::Hole(&mut b[0]))
let node = match kind {
CtrKind::Con(None) => Tree::Con { fst: Box::new(Tree::Era), snd: Box::new(Tree::Era) },
CtrKind::Dup(0) => Tree::Dup { fst: Box::new(Tree::Era), snd: Box::new(Tree::Era) },
CtrKind::Tup(None) => Tree::Con { fst: Box::new(Tree::Era), snd: Box::new(Tree::Era) },
_ => unreachable!(),
};
let ((a, b), node) = LoanedMut::loan_with(node, |t, l| match t {
Tree::Con { fst, snd } => (l.loan_mut(fst), l.loan_mut(snd)),
Tree::Dup { fst, snd } => (l.loan_mut(fst), l.loan_mut(snd)),
_ => unreachable!(),
});
(Place::Tree(node), Place::Hole(a), Place::Hole(b))
}
fn new_opr(&mut self) -> (Place<'t>, Place<'t>, Place<'t>) {
self.created_nodes += 1;
let ((fst, snd), node) =
LoanedMut::loan_with(Tree::Op { fst: Box::new(Tree::Era), snd: Box::new(Tree::Era) }, |t, l| {
let Tree::Op { fst, snd } = t else { unreachable!() };
LoanedMut::loan_with(Tree::Opr { fst: Box::new(Tree::Era), snd: Box::new(Tree::Era) }, |t, l| {
let Tree::Opr { fst, snd } = t else { unreachable!() };
(l.loan_mut(fst), l.loan_mut(snd))
});
(Place::Tree(node), Place::Hole(fst), Place::Hole(snd))
@ -319,7 +326,7 @@ impl<'t, 'l> EncodeTermState<'t, 'l> {
i
}
fn fan_kind(&mut self, fan: &FanKind, tag: &Tag) -> CtrKind {
fn fan_kind(&mut self, fan: &FanKind, tag: &crate::fun::Tag) -> CtrKind {
let lab = self.labels[*fan].generate(tag);
if *fan == FanKind::Tup { Tup(lab) } else { Dup(lab.unwrap()) }
}
@ -374,7 +381,8 @@ impl IndexMut<FanKind> for Labels {
impl LabelGenerator {
// If some tag and new generate a new label, otherwise return the generated label.
// If none use the implicit label counter.
fn generate(&mut self, tag: &Tag) -> Option<u16> {
fn generate(&mut self, tag: &crate::fun::Tag) -> Option<u16> {
use crate::fun::Tag;
match tag {
Tag::Named(_name) => {
todo!("Named tags not implemented for hvm32");
@ -393,7 +401,8 @@ impl LabelGenerator {
}
}
pub fn to_tag(&self, label: Option<u16>) -> Tag {
pub fn to_tag(&self, label: Option<u16>) -> crate::fun::Tag {
use crate::fun::Tag;
match label {
Some(label) => match self.label_to_name.get(&label) {
Some(name) => Tag::Named(name.clone()),
@ -415,31 +424,45 @@ impl LabelGenerator {
}
}
fn hole<T: Default>() -> T {
T::default()
}
impl Op {
fn to_native_tag(self) -> u32 {
fn to_native_tag(self) -> hvm::hvm::Tag {
match self {
Op::ADD => hvm::ast::OP_ADD,
Op::SUB => hvm::ast::OP_SUB,
Op::MUL => hvm::ast::OP_MUL,
Op::DIV => hvm::ast::OP_DIV,
Op::REM => hvm::ast::OP_REM,
Op::EQ => hvm::ast::OP_EQ,
Op::NEQ => hvm::ast::OP_NEQ,
Op::LT => hvm::ast::OP_LT,
Op::GT => hvm::ast::OP_GT,
Op::AND => hvm::ast::OP_AND,
Op::OR => hvm::ast::OP_OR,
Op::XOR => hvm::ast::OP_XOR,
Op::SHL => hvm::ast::OP_SHL,
Op::SHR => hvm::ast::OP_SHR,
Op::ADD => hvm::hvm::OP_ADD,
Op::SUB => hvm::hvm::OP_SUB,
Op::MUL => hvm::hvm::OP_MUL,
Op::DIV => hvm::hvm::OP_DIV,
Op::REM => hvm::hvm::OP_REM,
Op::EQ => hvm::hvm::OP_EQ,
Op::NEQ => hvm::hvm::OP_NEQ,
Op::LT => hvm::hvm::OP_LT,
Op::GT => hvm::hvm::OP_GT,
Op::AND => hvm::hvm::OP_AND,
Op::OR => hvm::hvm::OP_OR,
Op::XOR => hvm::hvm::OP_XOR,
Op::SHL => hvm::hvm::OP_SHL,
Op::SHR => hvm::hvm::OP_SHR,
Op::ATN => hvm::ast::OP_AND,
Op::LOG => hvm::ast::OP_OR,
Op::POW => hvm::ast::OP_XOR,
Op::ATN => hvm::hvm::OP_AND,
Op::LOG => hvm::hvm::OP_OR,
Op::POW => hvm::hvm::OP_XOR,
}
}
}
fn flip_sym(tag: hvm::hvm::Tag) -> hvm::hvm::Tag {
match tag {
hvm::hvm::OP_SUB => hvm::hvm::FP_SUB,
hvm::hvm::FP_SUB => hvm::hvm::OP_SUB,
hvm::hvm::OP_DIV => hvm::hvm::FP_DIV,
hvm::hvm::FP_DIV => hvm::hvm::OP_DIV,
hvm::hvm::OP_REM => hvm::hvm::FP_REM,
hvm::hvm::FP_REM => hvm::hvm::OP_REM,
hvm::hvm::OP_LT => hvm::hvm::OP_GT,
hvm::hvm::OP_GT => hvm::hvm::OP_LT,
hvm::hvm::OP_SHL => hvm::hvm::FP_SHL,
hvm::hvm::FP_SHL => hvm::hvm::OP_SHL,
hvm::hvm::OP_SHR => hvm::hvm::FP_SHR,
hvm::hvm::FP_SHR => hvm::hvm::OP_SHR,
_ => tag,
}
}

View File

@ -1,12 +1,11 @@
use crate::{
hvm::ast::{Book, Net, Tree},
maybe_grow,
};
use super::tree_children;
use crate::maybe_grow;
use hvm::ast::{Book, Net, Tree};
use std::collections::{HashMap, HashSet};
pub fn add_recursive_priority(book: &mut Book) {
// Direct dependencies
let deps = book.iter().map(|(nam, net)| (nam.clone(), dependencies(net))).collect::<HashMap<_, _>>();
let deps = book.defs.iter().map(|(nam, net)| (nam.clone(), dependencies(net))).collect::<HashMap<_, _>>();
// Recursive cycles
let cycles = cycles(&deps);
@ -14,7 +13,7 @@ pub fn add_recursive_priority(book: &mut Book) {
// For each function in the cycle, if there are redexes with the
// next ref in the cycle, add a priority to one of those redexes.
for i in 0 .. cycle.len() {
let cur = book.get_mut(&cycle[i]).unwrap();
let cur = book.defs.get_mut(&cycle[i]).unwrap();
let nxt = &cycle[(i + 1) % cycle.len()];
add_priority_next_in_cycle(cur, nxt);
}
@ -25,7 +24,7 @@ fn add_priority_next_in_cycle(net: &mut Net, nxt: &String) {
let mut count = 0;
// Count the number of recursive refs
for (_, a, b) in net.redexes.iter() {
for (_, a, b) in net.rbag.iter() {
if let Tree::Ref { nam } = a {
if nam == nxt {
count += 1;
@ -40,7 +39,7 @@ fn add_priority_next_in_cycle(net: &mut Net, nxt: &String) {
// If there are more than one recursive ref, add a priority to them.
if count > 1 {
for (pri, a, b) in net.redexes.iter_mut().rev() {
for (pri, a, b) in net.rbag.iter_mut().rev() {
if let Tree::Ref { nam } = a {
if nam == nxt {
*pri = true;
@ -105,7 +104,7 @@ fn find_cycles(
fn dependencies(net: &Net) -> HashSet<String> {
let mut deps = HashSet::new();
dependencies_tree(&net.root, &mut deps);
for (_, a, b) in &net.redexes {
for (_, a, b) in &net.rbag {
dependencies_tree(a, &mut deps);
dependencies_tree(b, &mut deps);
}
@ -116,7 +115,7 @@ fn dependencies_tree(tree: &Tree, deps: &mut HashSet<String>) {
if let Tree::Ref { nam, .. } = tree {
deps.insert(nam.clone());
} else {
for subtree in tree.children() {
for subtree in tree_children(tree) {
dependencies_tree(subtree, deps);
}
}

View File

@ -1,673 +0,0 @@
//! The textual language of HVMC.
//!
//! This file defines an AST for interaction nets, and functions to convert this
//! AST to/from the textual syntax.
//!
//! The grammar is documented in the repo README, as well as within the parser
//! methods, for convenience.
//!
//! The AST is based on the [interaction calculus].
//!
//! [interaction calculus]: https://en.wikipedia.org/wiki/Interaction_nets#Interaction_calculus
use arrayvec::ArrayVec;
use core::fmt;
use std::{collections::BTreeMap, iter, mem, str::FromStr};
use TSPL::Parser;
use crate::maybe_grow;
use super::util::{array_vec, deref};
/// The top level AST node, representing a collection of named nets.
///
/// This is a newtype wrapper around a `BTreeMap<String, Net>`, and is
/// dereferenceable to such.
#[derive(Clone, Hash, PartialEq, Eq, Debug, Default)]
pub struct Book {
pub nets: BTreeMap<String, Net>,
}
deref!(Book => self.nets: BTreeMap<String, Net>);
/// An AST node representing an interaction net with one free port.
///
/// The tree connected to the free port is stored in `root`. The active pairs in
/// the net -- trees connected by their roots -- are stored in `redexes`.
///
/// (The wiring connecting the leaves of all the trees is represented within the
/// trees via pairs of [`Tree::Var`] nodes with the same name.)
#[derive(Clone, Hash, PartialEq, Eq, Debug, Default)]
pub struct Net {
pub root: Tree,
pub redexes: Vec<(bool, Tree, Tree)>,
}
/// An AST node representing an interaction net tree.
///
/// Trees in interaction nets are inductively defined as either wires, or an
/// agent with all of its auxiliary ports (if any) connected to trees.
///
/// Here, the wires at the leaves of the tree are represented with
/// [`Tree::Var`], where the variable name is shared between both sides of the
/// wire.
#[derive(Hash, PartialEq, Eq, Debug, Default)]
pub enum Tree {
#[default]
/// A nilary eraser node.
Era,
/// A native 60-bit integer.
Num { val: u32 },
/// A nilary node, referencing a named net.
Ref { nam: String },
/// A n-ary interaction combinator.
Ctr {
/// The label of the combinator. (Combinators with the same label
/// annihilate, and combinators with different labels commute.)
lab: u16,
/// The auxiliary ports of this node.
///
/// - 0 ports: this behaves identically to an eraser node.
/// - 1 port: this behaves identically to a wire.
/// - 2 ports: this is a standard binary combinator node.
/// - 3+ ports: equivalent to right-chained binary nodes; `(a b c)` is
/// equivalent to `(a (b c))`.
///
/// The length of this vector must be less than [`MAX_ARITY`].
ports: Vec<Tree>,
},
/// A binary node representing an operation on native integers.
///
/// The principal port connects to the left operand.
Op {
/// An auxiliary port; connects to the right operand.
fst: Box<Tree>,
/// An auxiliary port; connects to the output.
snd: Box<Tree>,
},
/// A binary node representing a match on native integers.
///
/// The principal port connects to the integer to be matched on.
Mat {
/// An auxiliary port; connects to the zero branch.
zero: Box<Tree>,
/// An auxiliary port; connects to the a CTR with label 0 containing the
/// predecessor and the output of the succ branch.
succ: Box<Tree>,
/// An auxiliary port; connects to the output.
out: Box<Tree>,
},
/// One side of a wire; the other side will have the same name.
Var { nam: String },
}
pub const MAX_ARITY: usize = 8;
pub const MAX_ADT_VARIANTS: usize = MAX_ARITY - 1;
pub const MAX_ADT_FIELDS: usize = MAX_ARITY - 1;
impl Net {
pub fn trees(&self) -> impl Iterator<Item = &Tree> {
iter::once(&self.root).chain(self.redexes.iter().flat_map(|(_, x, y)| [x, y]))
}
pub fn trees_mut(&mut self) -> impl Iterator<Item = &mut Tree> {
iter::once(&mut self.root).chain(self.redexes.iter_mut().flat_map(|(_, x, y)| [x, y]))
}
}
impl Tree {
#[inline(always)]
pub fn children(&self) -> impl ExactSizeIterator + DoubleEndedIterator<Item = &Tree> {
ArrayVec::<_, MAX_ARITY>::into_iter(match self {
Tree::Era | Tree::Num { .. } | Tree::Ref { .. } | Tree::Var { .. } => array_vec::from_array([]),
Tree::Ctr { ports, .. } => array_vec::from_iter(ports),
Tree::Op { fst: rhs, snd: out, .. } => array_vec::from_array([rhs, out]),
Tree::Mat { zero, succ, out } => array_vec::from_array([zero, succ, out]),
})
}
#[inline(always)]
pub fn children_mut(&mut self) -> impl ExactSizeIterator + DoubleEndedIterator<Item = &mut Tree> {
ArrayVec::<_, MAX_ARITY>::into_iter(match self {
Tree::Era | Tree::Num { .. } | Tree::Ref { .. } | Tree::Var { .. } => array_vec::from_array([]),
Tree::Ctr { ports, .. } => array_vec::from_iter(ports),
Tree::Op { fst, snd } => array_vec::from_array([fst, snd]),
Tree::Mat { zero, succ, out } => array_vec::from_array([zero, succ, out]),
})
}
#[allow(unused)]
pub(crate) fn lab(&self) -> Option<u16> {
match self {
Tree::Ctr { lab, ports } if ports.len() >= 2 => Some(*lab),
_ => None,
}
}
pub fn legacy_mat(mut arms: Tree, out: Tree) -> Option<Tree> {
let Tree::Ctr { lab: 0, ports } = &mut arms else { None? };
let ports = mem::take(ports);
let Ok([zero, succ]) = <[_; 2]>::try_from(ports) else { None? };
let zero = Box::new(zero);
let succ = Box::new(succ);
Some(Tree::Mat { zero, succ, out: Box::new(out) })
}
}
pub const TY_SYM: u32 = 0x00;
pub const TY_U24: u32 = 0x01;
pub const TY_I24: u32 = 0x02;
pub const TY_F24: u32 = 0x03;
pub const OP_ADD: u32 = 0x04;
pub const OP_SUB: u32 = 0x05;
pub const FP_SUB: u32 = 0x06;
pub const OP_MUL: u32 = 0x07;
pub const OP_DIV: u32 = 0x08;
pub const FP_DIV: u32 = 0x09;
pub const OP_REM: u32 = 0x0A;
pub const FP_REM: u32 = 0x0B;
pub const OP_EQ: u32 = 0x0C;
pub const OP_NEQ: u32 = 0x0D;
pub const OP_LT: u32 = 0x0E;
pub const OP_GT: u32 = 0x0F;
pub const OP_AND: u32 = 0x10;
pub const OP_OR: u32 = 0x11;
pub const OP_XOR: u32 = 0x12;
pub const OP_SHL: u32 = 0x13;
pub const FP_SHL: u32 = 0x14;
pub const OP_SHR: u32 = 0x15;
pub const FP_SHR: u32 = 0x16;
pub fn flip_sym(val: u32) -> u32 {
match val {
OP_ADD => OP_ADD,
OP_SUB => FP_SUB,
OP_MUL => OP_MUL,
OP_DIV => FP_DIV,
OP_REM => FP_REM,
OP_EQ => OP_EQ,
OP_NEQ => OP_NEQ,
OP_LT => OP_GT,
OP_GT => OP_LT,
OP_AND => OP_AND,
OP_OR => OP_OR,
OP_XOR => OP_XOR,
FP_SUB => OP_SUB,
FP_DIV => OP_DIV,
FP_REM => OP_REM,
OP_SHL => FP_SHL,
OP_SHR => FP_SHR,
FP_SHL => OP_SHL,
FP_SHR => OP_SHR,
_ => unreachable!(),
}
}
pub fn new_sym(val: u32) -> u32 {
((val as u8 as u32) << 5) | TY_SYM
}
pub fn get_sym(val: u32) -> u32 {
(val >> 5) as u8 as u32
}
pub fn new_u24(val: u32) -> u32 {
(val << 5) | TY_U24
}
pub fn get_u24(val: u32) -> u32 {
val >> 5
}
pub fn new_i24(val: i32) -> u32 {
((val as u32) << 5) | TY_I24
}
pub fn get_i24(val: u32) -> i32 {
((val as i32) << 3) >> 8
}
pub fn new_f24(val: f32) -> u32 {
let bits = val.to_bits();
let mut shifted_bits = bits >> 8;
let lost_bits = bits & 0xFF;
shifted_bits += (lost_bits - ((lost_bits >> 7) & !shifted_bits)) >> 7; // round ties to even
shifted_bits |= u32::from((bits & 0x7F800000 == 0x7F800000) && (bits << 9 != 0)); // ensure NaNs don't become infinities
(shifted_bits << 5) | TY_F24
}
pub fn get_f24(val: u32) -> f32 {
f32::from_bits((val << 3) & 0xFFFFFF00)
}
pub fn get_typ(val: u32) -> u32 {
(val & 0x1F) as u8 as u32
}
pub fn partial_opr(a: u32, b: u32) -> u32 {
(b & !0x1F) | get_sym(a)
}
impl fmt::Display for Book {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, (name, net)) in self.iter().enumerate() {
if i != 0 {
f.write_str("\n\n")?;
}
write!(f, "@{name} = {net}")?;
}
Ok(())
}
}
impl fmt::Display for Net {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", &self.root)?;
for (pri, a, b) in &self.redexes {
write!(f, "\n &{} {a} ~ {b}", if *pri { "!" } else { "" })?;
}
Ok(())
}
}
impl fmt::Display for Tree {
#[allow(illegal_floating_point_literal_pattern)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
maybe_grow(move || match self {
Tree::Era => write!(f, "*"),
Tree::Ctr { lab, ports } => {
match lab {
0 => write!(f, "("),
1 => write!(f, "{{"),
_ => unreachable!(),
}?;
let mut space = *lab > 1;
for port in ports {
if space {
write!(f, " ")?;
}
write!(f, "{port}")?;
space = true;
}
match lab {
0 => write!(f, ")"),
1 => write!(f, "}}"),
_ => unreachable!(),
}?;
Ok(())
}
Tree::Var { nam } => write!(f, "{nam}"),
Tree::Ref { nam } => write!(f, "@{nam}"),
Tree::Num { val } => {
let numb = *val;
match get_typ(numb) {
TY_SYM => match get_sym(numb) as u8 as u32 {
OP_ADD => write!(f, "[+]"),
OP_SUB => write!(f, "[-]"),
OP_MUL => write!(f, "[*]"),
OP_DIV => write!(f, "[/]"),
OP_REM => write!(f, "[%]"),
OP_EQ => write!(f, "[=]"),
OP_LT => write!(f, "[<]"),
OP_GT => write!(f, "[>]"),
OP_AND => write!(f, "[&]"),
OP_OR => write!(f, "[|]"),
OP_XOR => write!(f, "[^]"),
OP_SHL => write!(f, "[<<]"),
OP_SHR => write!(f, "[>>]"),
FP_SUB => write!(f, "[:-]"),
FP_DIV => write!(f, "[:/]"),
FP_REM => write!(f, "[:%]"),
FP_SHL => write!(f, "[:<<]"),
FP_SHR => write!(f, "[:>>]"),
_ => write!(f, "[?]"),
},
TY_U24 => {
let val = get_u24(numb);
write!(f, "{}", val)
}
TY_I24 => {
let val = get_i24(numb);
write!(f, "{:+}", val)
}
TY_F24 => {
let val = get_f24(numb);
match val {
f32::INFINITY => write!(f, "+inf"),
f32::NEG_INFINITY => write!(f, "-inf"),
x if x.is_nan() => write!(f, "+NaN"),
_ => write!(f, "{:?}", val),
}
}
_ => {
let typ = get_typ(numb);
let val = get_u24(numb);
write!(
f,
"[{}{}]",
match typ {
OP_ADD => "+",
OP_SUB => "-",
OP_MUL => "*",
OP_DIV => "/",
OP_REM => "%",
OP_EQ => "=",
OP_NEQ => "!",
OP_LT => "<",
OP_GT => ">",
OP_AND => "&",
OP_OR => "|",
OP_XOR => "^",
OP_SHL => "<<",
OP_SHR => ">>",
FP_SUB => ":-",
FP_DIV => ":/",
FP_REM => ":%",
FP_SHL => ":<<",
FP_SHR => ":>>",
_ => "?",
},
val
)
}
}
}
Tree::Op { fst, snd } => write!(f, "$({fst} {snd})"),
Tree::Mat { zero, succ, out } => write!(f, "?(({zero} {succ}) {out})"),
})
}
}
// Manually implemented to avoid stack overflows.
impl Clone for Tree {
fn clone(&self) -> Tree {
maybe_grow(|| match self {
Tree::Era => Tree::Era,
Tree::Num { val } => Tree::Num { val: *val },
Tree::Ref { nam } => Tree::Ref { nam: nam.clone() },
Tree::Ctr { lab, ports } => Tree::Ctr { lab: *lab, ports: ports.clone() },
Tree::Op { fst, snd } => Tree::Op { fst: fst.clone(), snd: snd.clone() },
Tree::Mat { zero, succ, out } => Tree::Mat { zero: zero.clone(), succ: succ.clone(), out: out.clone() },
Tree::Var { nam } => Tree::Var { nam: nam.clone() },
})
}
}
// Drops non-recursively to avoid stack overflows.
impl Drop for Tree {
fn drop(&mut self) {
loop {
let mut i = self.children_mut().filter(|x| x.children().len() != 0);
let Some(x) = i.next() else { break };
if { i }.next().is_none() {
// There's only one child; move it up to be the new root.
*self = mem::take(x);
continue;
}
// Rotate the tree right:
// ```text
// a b
// / \ / \
// b e -> c a
// / \ / \
// c d d e
// ```
let d = mem::take(x.children_mut().next_back().unwrap());
let b = mem::replace(x, d);
let a = mem::replace(self, b);
mem::forget(mem::replace(self.children_mut().next_back().unwrap(), a));
}
}
}
// new_parser!(HvmcParser);
pub struct HvmcParser<'i> {
input: &'i str,
index: usize,
}
impl<'i> Parser<'i> for HvmcParser<'i> {
fn input(&mut self) -> &'i str {
self.input
}
fn index(&mut self) -> &mut usize {
&mut self.index
}
}
impl<'i> HvmcParser<'i> {
pub fn new(input: &'i str) -> Self {
Self { input, index: 0 }
}
}
impl<'i> HvmcParser<'i> {
/// Book = ("@" Name "=" Net)*
fn parse_book(&mut self) -> Result<Book, String> {
maybe_grow(move || {
let mut book = BTreeMap::new();
while self.consume("@").is_ok() {
let name = self.parse_name()?;
self.consume("=")?;
let net = self.parse_net()?;
book.insert(name, net);
}
Ok(Book { nets: book })
})
}
/// Net = Tree ("&" Tree "~" Tree)*
fn parse_net(&mut self) -> Result<Net, String> {
let mut redexes = Vec::new();
let root = self.parse_tree()?;
while self.consume("&").is_ok() {
let pri = if self.peek_one() == Some('!') {
self.advance_one();
true
} else {
false
};
let tree1 = self.parse_tree()?;
self.consume("~")?;
let tree2 = self.parse_tree()?;
redexes.push((pri, tree1, tree2));
}
Ok(Net { root, redexes })
}
fn parse_tree(&mut self) -> Result<Tree, String> {
maybe_grow(move || {
self.skip_trivia();
match self.peek_one() {
Some('*') => {
self.advance_one();
Ok(Tree::Era)
}
Some('(') => {
self.advance_one();
let fst = self.parse_tree()?;
self.skip_trivia();
let snd = self.parse_tree()?;
self.consume(")")?;
Ok(Tree::Ctr { lab: 0, ports: vec![fst, snd] })
}
Some('{') => {
self.advance_one();
let fst = self.parse_tree()?;
self.skip_trivia();
let snd = self.parse_tree()?;
self.consume("}")?;
Ok(Tree::Ctr { lab: 1, ports: vec![fst, snd] })
}
Some('@') => {
self.advance_one();
self.skip_trivia();
let nam = self.parse_name()?;
Ok(Tree::Ref { nam })
}
Some('$') => {
self.advance_one();
self.consume("(")?;
let fst = Box::new(self.parse_tree()?);
self.skip_trivia();
let snd = Box::new(self.parse_tree()?);
self.consume(")")?;
Ok(Tree::Op { fst, snd })
}
Some('?') => {
self.advance_one();
self.consume("(")?;
let zero = self.parse_tree()?;
let succ = self.parse_tree()?;
self.skip_trivia();
if self.peek_one() == Some(')') {
self.advance_one();
Tree::legacy_mat(zero, succ).ok_or_else(|| "invalid legacy match".to_owned())
} else {
let zero = Box::new(zero);
let succ = Box::new(succ);
let out = Box::new(self.parse_tree()?);
self.consume(">")?;
Ok(Tree::Mat { zero, succ, out })
}
}
_ => {
if let Some(c) = self.peek_one() {
if "0123456789+-[".contains(c) {
return Ok(Tree::Num { val: self.parse_numb()? });
}
}
let nam = self.parse_name()?;
Ok(Tree::Var { nam })
}
}
})
}
pub fn parse_numb_sym(&mut self) -> Result<u32, String> {
self.consume("[")?;
// Parses the symbol
let op = new_sym(match () {
_ if self.try_consume("+") => OP_ADD,
_ if self.try_consume("-") => OP_SUB,
_ if self.try_consume("*") => OP_MUL,
_ if self.try_consume("/") => OP_DIV,
_ if self.try_consume("%") => OP_REM,
_ if self.try_consume("=") => OP_EQ,
_ if self.try_consume("!") => OP_NEQ,
_ if self.try_consume("<") => OP_LT,
_ if self.try_consume(">") => OP_GT,
_ if self.try_consume("&") => OP_AND,
_ if self.try_consume("|") => OP_OR,
_ if self.try_consume("^") => OP_XOR,
_ if self.try_consume(">>") => OP_SHR,
_ if self.try_consume("<<") => OP_SHL,
_ if self.try_consume(":-") => FP_SUB,
_ if self.try_consume(":/") => FP_DIV,
_ if self.try_consume(":%") => FP_REM,
_ if self.try_consume(":>>") => FP_SHR,
_ if self.try_consume(":<<") => FP_SHL,
_ => self.expected("operator symbol")?,
});
self.skip_trivia();
// Syntax for partial operations, like `[*2]`
let num = if self.peek_one() != Some(']') { partial_opr(op, self.parse_numb_lit()?) } else { op };
// Closes symbol bracket
self.consume("]")?;
// Returns the symbol
Ok(num)
}
pub fn parse_numb_lit(&mut self) -> Result<u32, String> {
let num = self.take_while(|x| x.is_alphanumeric() || x == '+' || x == '-' || x == '.');
Ok(if num.contains('.') || num.contains("inf") || num.contains("NaN") {
let val: f32 = num.parse().map_err(|err| format!("invalid number literal: {err}"))?;
new_f24(val)
} else if num.starts_with('+') || num.starts_with('-') {
let val = Self::parse_int(&num[1 ..])? as i32;
new_i24(if num.starts_with('-') { -val } else { val })
} else {
let val = Self::parse_int(num)? as u32;
new_u24(val)
})
}
fn parse_int(input: &str) -> Result<u64, String> {
if let Some(rest) = input.strip_prefix("0x") {
u64::from_str_radix(rest, 16).map_err(|err| format!("{err:?}"))
} else if let Some(rest) = input.strip_prefix("0b") {
u64::from_str_radix(rest, 2).map_err(|err| format!("{err:?}"))
} else {
input.parse::<u64>().map_err(|err| format!("{err:?}"))
}
}
pub fn parse_numb(&mut self) -> Result<u32, String> {
self.skip_trivia();
// Parses symbols (SYM)
if let Some('[') = self.peek_one() {
self.parse_numb_sym()
// Parses numbers (U24,I24,F24)
} else {
self.parse_numb_lit()
}
}
/// Name = /[a-zA-Z0-9_.$]+/
fn parse_name(&mut self) -> Result<String, String> {
let name = self.take_while(|c| c.is_alphanumeric() || c == '_' || c == '.' || c == '-' || c == '/');
if name.is_empty() {
return self.expected("name");
}
Ok(name.to_owned())
}
fn try_consume(&mut self, str: &str) -> bool {
let matches = self.peek_many(str.len()) == Some(str);
if matches {
self.advance_many(str.len());
}
matches
}
}
/// Parses the input with the callback, ensuring that the whole input is
/// consumed.
fn parse_eof<'i, T>(
input: &'i str,
parse_fn: impl Fn(&mut HvmcParser<'i>) -> Result<T, String>,
) -> Result<T, String> {
let mut parser = HvmcParser::new(input);
let out = parse_fn(&mut parser)?;
if parser.index != parser.input.len() {
return Err("Unable to parse the whole input. Is this not an hvmc file?".to_owned());
}
Ok(out)
}
impl FromStr for Book {
type Err = String;
fn from_str(str: &str) -> Result<Self, Self::Err> {
parse_eof(str, HvmcParser::parse_book)
}
}
impl FromStr for Net {
type Err = String;
fn from_str(str: &str) -> Result<Self, Self::Err> {
parse_eof(str, HvmcParser::parse_net)
}
}
impl FromStr for Tree {
type Err = String;
fn from_str(str: &str) -> Result<Self, Self::Err> {
parse_eof(str, HvmcParser::parse_tree)
}
}

View File

@ -1,12 +1,13 @@
use super::ast::Book;
use super::tree_children;
use crate::{diagnostics::Diagnostics, fun::Name};
use hvm::ast::{Book, Net, Tree};
pub const MAX_NET_SIZE: usize = 64;
pub fn check_net_sizes(book: &Book, diagnostics: &mut Diagnostics) -> Result<(), Diagnostics> {
diagnostics.start_pass();
for (name, net) in &book.nets {
for (name, net) in &book.defs {
let nodes = count_nodes(net);
if nodes > MAX_NET_SIZE {
diagnostics.add_rule_error(
@ -20,19 +21,19 @@ pub fn check_net_sizes(book: &Book, diagnostics: &mut Diagnostics) -> Result<(),
}
/// Utility function to count the amount of nodes in an hvm-core AST net
pub fn count_nodes<'l>(net: &'l super::ast::Net) -> usize {
let mut visit: Vec<&'l super::ast::Tree> = vec![&net.root];
pub fn count_nodes(net: &Net) -> usize {
let mut visit: Vec<&Tree> = vec![&net.root];
let mut count = 0usize;
for (_, l, r) in &net.redexes {
for (_, l, r) in &net.rbag {
visit.push(l);
visit.push(r);
}
while let Some(tree) = visit.pop() {
// If it is not 0-ary, then we'll count it as a node.
if tree.children().next().is_some() {
if tree_children(tree).next().is_some() {
count += 1;
}
for subtree in tree.children() {
for subtree in tree_children(tree) {
visit.push(subtree);
}
}

View File

@ -53,21 +53,22 @@
//!
//! The pass also reduces subnets such as `(* *) -> *`
use crate::hvm::ast::{Net, Tree};
use crate::hvm::net_trees_mut;
use super::{tree_children, tree_children_mut};
use core::ops::RangeFrom;
use hvm::ast::{Net, Tree};
use std::collections::HashMap;
impl Net {
/// Carries out simple eta-reduction
pub fn eta_reduce(&mut self) {
let mut phase1 = Phase1::default();
for tree in self.trees() {
phase1.walk_tree(tree);
}
let mut phase2 = Phase2 { nodes: phase1.nodes, index: 0 .. };
for tree in self.trees_mut() {
phase2.reduce_tree(tree);
}
/// Carries out simple eta-reduction
pub fn eta_reduce_hvm_net(net: &mut Net) {
let mut phase1 = Phase1::default();
for tree in net_trees_mut(net) {
phase1.walk_tree(tree);
}
let mut phase2 = Phase2 { nodes: phase1.nodes, index: 0 .. };
for tree in net_trees_mut(net) {
phase2.reduce_tree(tree);
}
}
@ -75,13 +76,12 @@ impl Net {
enum NodeType {
Ctr(u16),
Var(isize),
Num(u32),
Era,
Other,
Hole,
}
#[derive(Default)]
#[derive(Default, Debug)]
struct Phase1<'a> {
vars: HashMap<&'a str, usize>,
nodes: Vec<NodeType>,
@ -90,14 +90,15 @@ struct Phase1<'a> {
impl<'a> Phase1<'a> {
fn walk_tree(&mut self, tree: &'a Tree) {
match tree {
Tree::Ctr { lab, ports } => {
let last_port = ports.len() - 1;
for (idx, i) in ports.iter().enumerate() {
if idx != last_port {
self.nodes.push(NodeType::Ctr(*lab));
}
self.walk_tree(i);
}
Tree::Con { fst, snd } => {
self.nodes.push(NodeType::Ctr(0));
self.walk_tree(fst);
self.walk_tree(snd);
}
Tree::Dup { fst, snd } => {
self.nodes.push(NodeType::Ctr(1));
self.walk_tree(fst);
self.walk_tree(snd);
}
Tree::Var { nam } => {
if let Some(i) = self.vars.get(&**nam) {
@ -110,10 +111,9 @@ impl<'a> Phase1<'a> {
}
}
Tree::Era => self.nodes.push(NodeType::Era),
Tree::Num { val } => self.nodes.push(NodeType::Num(*val)),
_ => {
self.nodes.push(NodeType::Other);
for i in tree.children() {
for i in tree_children(tree) {
self.walk_tree(i);
}
}
@ -127,42 +127,42 @@ struct Phase2 {
}
impl Phase2 {
fn reduce_ctr(&mut self, lab: u16, ports: &mut Vec<Tree>, skip: usize) -> NodeType {
if skip == ports.len() {
return NodeType::Other;
}
if skip == ports.len() - 1 {
return self.reduce_tree(&mut ports[skip]);
}
let head_index = self.index.next().unwrap();
let a = self.reduce_tree(&mut ports[skip]);
let b = self.reduce_ctr(lab, ports, skip + 1);
if a == b {
let reducible = match a {
NodeType::Var(delta) => self.nodes[head_index.wrapping_add_signed(delta)] == NodeType::Ctr(lab),
NodeType::Era => true,
_ => false,
};
if reducible {
ports.pop();
return a;
fn reduce_ctr(&mut self, tree: &mut Tree, idx: usize) -> NodeType {
if let Tree::Con { fst, snd } | Tree::Dup { fst, snd } = tree {
let fst_typ = self.reduce_tree(fst);
let snd_typ = self.reduce_tree(snd);
// If both children are variables with the same offset, and their parent is a ctr of the same label,
// then they are eta-reducible and we replace the current node with the first variable.
match (fst_typ, snd_typ) {
(NodeType::Var(off_lft), NodeType::Var(off_rgt)) => {
if off_lft == off_rgt && self.nodes[idx] == self.nodes[(idx as isize + off_lft) as usize] {
let Tree::Var { nam } = fst.as_mut() else { unreachable!() };
*tree = Tree::Var { nam: std::mem::take(nam) };
return NodeType::Var(off_lft);
}
}
(NodeType::Era, NodeType::Era) => {
*tree = Tree::Era;
return NodeType::Era;
}
_ => {}
}
}
NodeType::Ctr(lab)
}
fn reduce_tree(&mut self, tree: &mut Tree) -> NodeType {
if let Tree::Ctr { lab, ports } = tree {
let ty = self.reduce_ctr(*lab, ports, 0);
if ports.len() == 1 {
*tree = ports.pop().unwrap();
}
ty
self.nodes[idx]
} else {
let index = self.index.next().unwrap();
for i in tree.children_mut() {
self.reduce_tree(i);
unreachable!()
}
}
fn reduce_tree(&mut self, tree: &mut Tree) -> NodeType {
let idx = self.index.next().unwrap();
match tree {
Tree::Con { .. } | Tree::Dup { .. } => self.reduce_ctr(tree, idx),
_ => {
for child in tree_children_mut(tree) {
self.reduce_tree(child);
}
self.nodes[idx]
}
self.nodes[index]
}
}
}

75
src/hvm/inline.rs Normal file
View File

@ -0,0 +1,75 @@
use super::{net_trees_mut, tree_children, tree_children_mut};
use crate::maybe_grow;
use core::ops::BitOr;
use hvm::ast::{Book, Net, Tree};
use std::collections::{HashMap, HashSet};
pub fn inline_hvm_book(book: &mut Book) -> Result<HashSet<String>, String> {
let mut state = InlineState::default();
state.populate_inlinees(book)?;
let mut all_changed = HashSet::new();
for (name, net) in &mut book.defs {
let mut inlined = false;
for tree in net_trees_mut(net) {
inlined |= state.inline_into(tree);
}
if inlined {
all_changed.insert(name.to_owned());
}
}
Ok(all_changed)
}
#[derive(Debug, Default)]
struct InlineState {
inlinees: HashMap<String, Tree>,
}
impl InlineState {
fn populate_inlinees(&mut self, book: &Book) -> Result<(), String> {
for (name, net) in &book.defs {
if should_inline(net) {
// Detect cycles with tortoise and hare algorithm
let mut hare = &net.root;
let mut tortoise = &net.root;
// Whether or not the tortoise should take a step
let mut parity = false;
while let Tree::Ref { nam, .. } = hare {
let Some(net) = &book.defs.get(nam) else { break };
if should_inline(net) {
hare = &net.root;
} else {
break;
}
if parity {
let Tree::Ref { nam: tortoise_nam, .. } = tortoise else { unreachable!() };
if tortoise_nam == nam {
Err(format!("infinite reference cycle in `@{nam}`"))?;
}
tortoise = &book.defs[tortoise_nam].root;
}
parity = !parity;
}
self.inlinees.insert(name.to_owned(), hare.clone());
}
}
Ok(())
}
fn inline_into(&self, tree: &mut Tree) -> bool {
maybe_grow(|| {
let Tree::Ref { nam, .. } = &*tree else {
return tree_children_mut(tree).map(|t| self.inline_into(t)).fold(false, bool::bitor);
};
if let Some(inlined) = self.inlinees.get(nam) {
*tree = inlined.clone();
true
} else {
false
}
})
}
}
fn should_inline(net: &Net) -> bool {
net.rbag.is_empty() && tree_children(&net.root).next().is_none()
}

View File

@ -1,7 +1,153 @@
use crate::{fun::display::DisplayFn, multi_iterator};
use hvm::ast::{Net, Tree};
pub mod add_recursive_priority;
pub mod ast;
pub mod check_net_size;
pub mod eta_reduce;
pub mod inline;
pub mod mutual_recursion;
pub mod ops;
pub mod transform;
pub mod util;
pub mod prune;
pub fn tree_children(tree: &Tree) -> impl DoubleEndedIterator<Item = &Tree> + Clone {
multi_iterator!(ChildrenIter { Zero, Two });
match tree {
Tree::Var { .. } | Tree::Ref { .. } | Tree::Era | Tree::Num { .. } => ChildrenIter::Zero([]),
Tree::Con { fst, snd } | Tree::Dup { fst, snd } | Tree::Opr { fst, snd } | Tree::Swi { fst, snd } => {
ChildrenIter::Two([fst.as_ref(), snd.as_ref()])
}
}
}
pub fn tree_children_mut(tree: &mut Tree) -> impl DoubleEndedIterator<Item = &mut Tree> {
multi_iterator!(ChildrenIter { Zero, Two });
match tree {
Tree::Var { .. } | Tree::Ref { .. } | Tree::Era | Tree::Num { .. } => ChildrenIter::Zero([]),
Tree::Con { fst, snd } | Tree::Dup { fst, snd } | Tree::Opr { fst, snd } | Tree::Swi { fst, snd } => {
ChildrenIter::Two([fst.as_mut(), snd.as_mut()])
}
}
}
pub fn net_trees(net: &Net) -> impl DoubleEndedIterator<Item = &Tree> + Clone {
[&net.root].into_iter().chain(net.rbag.iter().flat_map(|(_, fst, snd)| [fst, snd]))
}
pub fn net_trees_mut(net: &mut Net) -> impl DoubleEndedIterator<Item = &mut Tree> {
[&mut net.root].into_iter().chain(net.rbag.iter_mut().flat_map(|(_, fst, snd)| [fst, snd]))
}
pub fn display_hvm_book(book: &hvm::ast::Book) -> impl std::fmt::Display + '_ {
DisplayFn(|f| {
for (nam, def) in book.defs.iter() {
writeln!(f, "@{} = {}", nam, display_hvm_tree(&def.root))?;
for (pri, a, b) in def.rbag.iter() {
writeln!(f, " &{}{} ~ {}", if *pri { "!" } else { " " }, display_hvm_tree(a), display_hvm_tree(b))?;
}
writeln!(f)?;
}
Ok(())
})
}
// TODO: We have to reimplement these because hvm prints partially applied numbers incorrectly.
// https://github.com/HigherOrderCO/HVM/issues/350
pub fn display_hvm_numb(numb: &hvm::ast::Numb) -> impl std::fmt::Display + '_ {
let numb = hvm::hvm::Numb(numb.0);
match numb.get_typ() {
hvm::hvm::TY_SYM => match numb.get_sym() as hvm::hvm::Tag {
hvm::hvm::OP_ADD => "[+]".to_string(),
hvm::hvm::OP_SUB => "[-]".to_string(),
hvm::hvm::FP_SUB => "[:-]".to_string(),
hvm::hvm::OP_MUL => "[*]".to_string(),
hvm::hvm::OP_DIV => "[/]".to_string(),
hvm::hvm::FP_DIV => "[:/]".to_string(),
hvm::hvm::OP_REM => "[%]".to_string(),
hvm::hvm::FP_REM => "[:%]".to_string(),
hvm::hvm::OP_EQ => "[=]".to_string(),
hvm::hvm::OP_NEQ => "[!]".to_string(),
hvm::hvm::OP_LT => "[<]".to_string(),
hvm::hvm::OP_GT => "[>]".to_string(),
hvm::hvm::OP_AND => "[&]".to_string(),
hvm::hvm::OP_OR => "[|]".to_string(),
hvm::hvm::OP_XOR => "[^]".to_string(),
hvm::hvm::OP_SHL => "[<<]".to_string(),
hvm::hvm::FP_SHL => "[:<<]".to_string(),
hvm::hvm::OP_SHR => "[>>]".to_string(),
hvm::hvm::FP_SHR => "[:>>]".to_string(),
_ => "[?]".to_string(),
},
hvm::hvm::TY_U24 => {
let val = numb.get_u24();
format!("{}", val)
}
hvm::hvm::TY_I24 => {
let val = numb.get_i24();
format!("{:+}", val)
}
hvm::hvm::TY_F24 => {
let val = numb.get_f24();
if val.is_infinite() {
if val.is_sign_positive() { "+inf".to_string() } else { "-inf".to_string() }
} else if val.is_nan() {
"+NaN".to_string()
} else {
format!("{:?}", val)
}
}
_ => {
let typ = numb.get_typ();
let val = numb.get_u24();
format!(
"[{}{}]",
match typ {
hvm::hvm::OP_ADD => "+",
hvm::hvm::OP_SUB => "-",
hvm::hvm::FP_SUB => ":-",
hvm::hvm::OP_MUL => "*",
hvm::hvm::OP_DIV => "/",
hvm::hvm::FP_DIV => ":/",
hvm::hvm::OP_REM => "%",
hvm::hvm::FP_REM => ":%",
hvm::hvm::OP_EQ => "=",
hvm::hvm::OP_NEQ => "!",
hvm::hvm::OP_LT => "<",
hvm::hvm::OP_GT => ">",
hvm::hvm::OP_AND => "&",
hvm::hvm::OP_OR => "|",
hvm::hvm::OP_XOR => "^",
hvm::hvm::OP_SHL => "<<",
hvm::hvm::FP_SHL => ":<<",
hvm::hvm::OP_SHR => ">>",
hvm::hvm::FP_SHR => ":>>",
_ => "?",
},
val
)
}
}
}
pub fn display_hvm_tree(tree: &hvm::ast::Tree) -> impl std::fmt::Display + '_ {
match tree {
Tree::Var { nam } => nam.to_string(),
Tree::Ref { nam } => format!("@{}", nam),
Tree::Era => "*".to_string(),
Tree::Num { val } => format!("{}", display_hvm_numb(val)),
Tree::Con { fst, snd } => format!("({} {})", display_hvm_tree(fst), display_hvm_tree(snd)),
Tree::Dup { fst, snd } => format!("{{{} {}}}", display_hvm_tree(fst), display_hvm_tree(snd)),
Tree::Opr { fst, snd } => format!("$({} {})", display_hvm_tree(fst), display_hvm_tree(snd)),
Tree::Swi { fst, snd } => format!("?({} {})", display_hvm_tree(fst), display_hvm_tree(snd)),
}
}
pub fn display_hvm_net(net: &hvm::ast::Net) -> impl std::fmt::Display + '_ {
let mut s = display_hvm_tree(&net.root).to_string();
for (par, fst, snd) in &net.rbag {
s.push_str(" & ");
s.push_str(if *par { "!" } else { "" });
s.push_str(&display_hvm_tree(fst).to_string());
s.push_str(" ~ ");
s.push_str(&display_hvm_tree(snd).to_string());
}
s
}

View File

@ -13,7 +13,7 @@ It is lazy when it's an argument ('(x Foo)') or when it's used linearly ('let x
to:
'Foo = λa λb (b (λc λa (Foo a c)) (λa a) a)'
which is lifted to:
'Foo = λa λb (b Foo__C1 Foo_C2 a)'
'Foo = λa λb (b Foo__C1 Foo__C2 a)'
- Replace non-linear 'let' expressions with 'use' expressions. For example, change:
'Foo = λf let x = Foo; (f x x)'
to:
@ -22,5 +22,5 @@ It is lazy when it's an argument ('(x Foo)') or when it's used linearly ('let x
'Foo = λf (f Foo Foo)'
- If disabled, re-enable the default 'float-combinators' and 'linearize-matches' compiler options.
For more information, visit: https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md.
For more information, visit: https://github.com/HigherOrderCO/Bend/blob/main/docs/lazy-definitions.md.
To disable this check, use the "-Arecursion-cycle" compiler option.

View File

@ -1,13 +1,13 @@
use super::tree_children;
use crate::{
diagnostics::{Diagnostics, WarningType, ERR_INDENT_SIZE},
fun::transform::definition_merge::MERGE_SEPARATOR,
maybe_grow,
};
use hvm::ast::{Book, Tree};
use indexmap::{IndexMap, IndexSet};
use std::fmt::Debug;
use super::ast::{Book, Tree};
type Ref = String;
type Stack<T> = Vec<T>;
type RefSet = IndexSet<Ref>;
@ -109,13 +109,9 @@ impl Graph {
fn collect_refs(current: Ref, tree: &Tree, graph: &mut Graph) {
maybe_grow(|| match tree {
Tree::Ref { nam, .. } => graph.add(current, nam.clone()),
Tree::Ctr { ports, .. } => {
if let Some(last) = ports.last() {
collect_refs(current, last, graph);
}
}
Tree::Con { fst: _, snd } => collect_refs(current.clone(), snd, graph),
tree => {
for subtree in tree.children() {
for subtree in tree_children(tree) {
collect_refs(current.clone(), subtree, graph);
}
}
@ -126,12 +122,12 @@ impl From<&Book> for Graph {
fn from(book: &Book) -> Self {
let mut graph = Self::new();
for (r#ref, net) in book.iter() {
for (r#ref, net) in book.defs.iter() {
// Collect active refs from the root.
collect_refs(r#ref.clone(), &net.root, &mut graph);
// Collect active refs from redexes.
for (_, left, right) in net.redexes.iter() {
for (_, left, right) in net.rbag.iter() {
if let Tree::Ref { nam, .. } = left {
graph.add(r#ref.clone(), nam.clone());
}

View File

@ -1,222 +0,0 @@
mod num;
mod word;
use self::{
num::Numeric,
word::{FromWord, ToWord},
};
use core::{
cmp::{Eq, Ord},
fmt,
};
use std::mem;
use super::util::bi_enum;
bi_enum! {
#[repr(u8)]
/// The type of a numeric operation.
///
/// This dictates how the bits of the operands will be interpreted,
/// and the return type of the operation.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Ty {
"u8": U8 = 0,
"u16": U16 = 1,
"u32": U32 = 2,
"u60": U60 = 3,
"i8": I8 = 4,
"i16": I16 = 5,
"i32": I32 = 6,
"f32": F32 = 7,
}
}
impl Ty {
#[inline(always)]
fn is_int(&self) -> bool {
*self < Self::F32
}
}
bi_enum! {
#[repr(u8)]
/// Native operations on numerics (u8, u16, u32, u60, i8, i16, i32, f32).
///
/// Each operation has a swapped counterpart (accessible with `.swap()`),
/// where the order of the operands is swapped.
///
/// Operations without an already-named counterpart (e.g. `Add <-> Add` and
/// `Lt <-> Gt`) are suffixed with `$`/`S`: `(-$ 1 2) = (- 2 1) = 1`.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Op {
"+": Add = 0,
"-": Sub = 1,
"-$": SubS = 2,
"*": Mul = 3,
"/": Div = 4,
"/$": DivS = 5,
"%": Rem = 6,
"%$": RemS = 7,
"&": And = 8,
"|": Or = 9,
"^": Xor = 10,
"<<": Shl = 11,
"<<$": ShlS = 12,
">>": Shr = 13,
">>$": ShrS = 14,
// operators returning ints should go after `Eq`
"==": Eq = 15,
"!=": Ne = 16,
"<": Lt = 17,
">": Gt = 18,
"<=": Le = 19,
">=": Ge = 20,
}
}
impl Op {
/// Returns this operation's swapped counterpart.
///
/// For all `op, a, b`, `op.swap().op(a, b) == op.op(b, a)`.
#[inline]
pub fn swap(self) -> Self {
match self {
Self::Add => Self::Add,
Self::Sub => Self::SubS,
Self::SubS => Self::Sub,
Self::Mul => Self::Mul,
Self::Div => Self::DivS,
Self::DivS => Self::Div,
Self::Rem => Self::RemS,
Self::RemS => Self::Rem,
Self::And => Self::And,
Self::Or => Self::Or,
Self::Xor => Self::Xor,
Self::Shl => Self::ShlS,
Self::ShlS => Self::Shl,
Self::Shr => Self::ShrS,
Self::ShrS => Self::Shr,
Self::Eq => Self::Eq,
Self::Ne => Self::Ne,
Self::Lt => Self::Gt,
Self::Gt => Self::Lt,
Self::Le => Self::Ge,
Self::Ge => Self::Le,
}
}
fn op<T: Numeric + FromWord + ToWord>(self, a: u64, b: u64) -> u64 {
let a = T::from_word(a);
let b = T::from_word(b);
match self {
Self::Add => T::add(a, b).to_word(),
Self::Sub => T::sub(a, b).to_word(),
Self::SubS => T::sub(b, a).to_word(),
Self::Mul => T::mul(a, b).to_word(),
Self::Div => T::div(a, b).to_word(),
Self::DivS => T::div(b, a).to_word(),
Self::Rem => T::rem(a, b).to_word(),
Self::RemS => T::rem(b, a).to_word(),
Self::And => T::and(a, b).to_word(),
Self::Or => T::or(a, b).to_word(),
Self::Xor => T::xor(a, b).to_word(),
Self::Shl => T::shl(a, b).to_word(),
Self::ShlS => T::shl(b, a).to_word(),
Self::Shr => T::shr(a, b).to_word(),
Self::ShrS => T::shr(b, a).to_word(),
// comparison operators return an integer, which is not necessarily a `T`.
Self::Eq => (a == b).into(),
Self::Ne => (a != b).into(),
Self::Lt => (a < b).into(),
Self::Le => (a <= b).into(),
Self::Gt => (a > b).into(),
Self::Ge => (a >= b).into(),
}
}
#[inline(always)]
fn is_comparison(&self) -> bool {
*self >= Self::Eq
}
}
/// A numeric operator.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C, align(2))]
pub struct TypedOp {
/// The type of the operands.
pub ty: Ty,
/// The operation. An opaque type whose interpretation depends on `ty`.
pub op: Op,
}
impl TypedOp {
pub unsafe fn from_unchecked(val: u16) -> Self {
mem::transmute(val)
}
/// Whether this operation returns an int.
#[inline(always)]
pub fn is_int(&self) -> bool {
self.ty.is_int() || self.op.is_comparison()
}
pub fn swap(self) -> Self {
Self { op: self.op.swap(), ty: self.ty }
}
#[inline]
pub fn op(self, a: u64, b: u64) -> u64 {
const U60: u64 = 0xFFF_FFFF_FFFF_FFFF;
match self.ty {
Ty::I8 => self.op.op::<i8>(a, b),
Ty::I16 => self.op.op::<i16>(a, b),
Ty::I32 => self.op.op::<i32>(a, b),
Ty::U8 => self.op.op::<u8>(a, b),
Ty::U16 => self.op.op::<u16>(a, b),
Ty::U32 => self.op.op::<u32>(a, b),
Ty::U60 => self.op.op::<u64>(a, b) & U60,
Ty::F32 => self.op.op::<f32>(a, b),
}
}
}
impl fmt::Display for TypedOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.ty {
Ty::U60 => write!(f, "{}", self.op),
_ => write!(f, "{}.{}", self.ty, self.op),
}
}
}
impl TryFrom<u16> for TypedOp {
type Error = ();
fn try_from(value: u16) -> Result<Self, Self::Error> {
let [ty, op] = value.to_ne_bytes();
Ok(Self { ty: Ty::try_from(ty)?, op: Op::try_from(op)? })
}
}
impl From<TypedOp> for u16 {
fn from(TypedOp { ty, op }: TypedOp) -> Self {
u16::from_ne_bytes([ty as u8, op as u8])
}
}
// #[cfg_attr(feature = "std", derive(Error))]
// #[derive(Debug)]
// pub enum OpParseError {
// #[cfg_attr(feature = "std", error("invalid type: {0}"))]
// Type(String),
// #[cfg_attr(feature = "std", error("invalid operator: {0}"))]
// Op(String),
// }

View File

@ -1,58 +0,0 @@
#[rustfmt::skip]
pub trait Numeric: PartialEq + PartialOrd + Sized {
const ZERO: Self;
fn add(_: Self, _: Self) -> Self { Self::ZERO }
fn sub(_: Self, _: Self) -> Self { Self::ZERO }
fn mul(_: Self, _: Self) -> Self { Self::ZERO }
fn div(_: Self, _: Self) -> Self { Self::ZERO }
fn rem(_: Self, _: Self) -> Self { Self::ZERO }
fn and(_: Self, _: Self) -> Self { Self::ZERO }
fn or(_: Self, _: Self) -> Self { Self::ZERO }
fn xor(_: Self, _: Self) -> Self { Self::ZERO }
fn shl(_: Self, _: Self) -> Self { Self::ZERO }
fn shr(_: Self, _: Self) -> Self { Self::ZERO }
}
macro_rules! impl_numeric {
( $($ty:ty),+ ) => {
$(
impl Numeric for $ty {
const ZERO: Self = 0;
fn add(a: Self, b: Self) -> Self { a.wrapping_add(b) }
fn sub(a: Self, b: Self) -> Self { a.wrapping_sub(b) }
fn mul(a: Self, b: Self) -> Self { a.wrapping_mul(b) }
fn div(a: Self, b: Self) -> Self { a.checked_div(b).unwrap_or(0) }
fn rem(a: Self, b: Self) -> Self { a.checked_rem(b).unwrap_or(0) }
fn and(a: Self, b: Self) -> Self { a & b }
fn or(a: Self, b: Self) -> Self { a | b }
fn xor(a: Self, b: Self) -> Self { a ^ b }
fn shl(a: Self, b: Self) -> Self { a.wrapping_shl(b as u32) }
fn shr(a: Self, b: Self) -> Self { a.wrapping_shr(b as u32) }
}
)*
}
}
impl_numeric! { u8, u16, u32, u64, i8, i16, i32 }
impl Numeric for f32 {
const ZERO: Self = 0.0;
fn add(a: Self, b: Self) -> Self {
a + b
}
fn sub(a: Self, b: Self) -> Self {
a - b
}
fn mul(a: Self, b: Self) -> Self {
a * b
}
fn div(a: Self, b: Self) -> Self {
a / b
}
fn rem(a: Self, b: Self) -> Self {
a % b
}
}

View File

@ -1,43 +0,0 @@
pub trait FromWord {
fn from_word(bits: u64) -> Self;
}
pub trait ToWord {
fn to_word(self) -> u64;
}
macro_rules! impl_word {
( $($ty:ty),+ ) => {
$(
impl FromWord for $ty {
#[inline(always)]
fn from_word(bits: u64) -> Self {
bits as Self
}
}
impl ToWord for $ty {
#[inline(always)]
fn to_word(self) -> u64 {
self as u64
}
}
)*
};
}
impl_word! { u8, u16, u32, u64, i8, i16, i32 }
impl FromWord for f32 {
#[inline(always)]
fn from_word(bits: u64) -> Self {
f32::from_bits(bits as u32)
}
}
impl ToWord for f32 {
#[inline(always)]
fn to_word(self) -> u64 {
self.to_bits() as u64
}
}

39
src/hvm/prune.rs Normal file
View File

@ -0,0 +1,39 @@
use super::{net_trees, tree_children};
use crate::maybe_grow;
use hvm::ast::{Book, Tree};
use std::collections::HashSet;
pub fn prune_hvm_book(book: &mut Book, entrypoints: &[String]) {
let mut state = PruneState { book, unvisited: book.defs.keys().map(|x| x.to_owned()).collect() };
for name in entrypoints {
state.visit_def(name);
}
let unvisited = state.unvisited;
for name in unvisited {
book.defs.remove(&name);
}
}
struct PruneState<'a> {
book: &'a Book,
unvisited: HashSet<String>,
}
impl<'a> PruneState<'a> {
fn visit_def(&mut self, name: &str) {
if self.unvisited.remove(name) {
for tree in net_trees(&self.book.defs[name]) {
self.visit_tree(tree);
}
}
}
fn visit_tree(&mut self, tree: &Tree) {
maybe_grow(|| {
if let Tree::Ref { nam, .. } = tree {
self.visit_def(nam);
} else {
tree_children(tree).for_each(|t| self.visit_tree(t));
}
})
}
}

View File

@ -1,17 +0,0 @@
use core::fmt;
pub mod eta_reduce;
pub mod inline;
pub mod prune;
pub enum TransformError {
InfiniteRefCycle(String),
}
impl fmt::Display for TransformError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TransformError::InfiniteRefCycle(r) => write!(f, "infinite reference cycle in `@{r}`"),
}
}
}

View File

@ -1,81 +0,0 @@
use super::TransformError;
use crate::{
hvm::ast::{Book, Net, Tree},
maybe_grow,
};
use core::ops::BitOr;
use std::collections::{HashMap, HashSet};
impl Book {
pub fn inline(&mut self) -> Result<HashSet<String>, TransformError> {
let mut state = InlineState::default();
state.populate_inlinees(self)?;
let mut all_changed = HashSet::new();
for (name, net) in &mut self.nets {
let mut inlined = false;
for tree in net.trees_mut() {
inlined |= state.inline_into(tree);
}
if inlined {
all_changed.insert(name.to_owned());
}
}
Ok(all_changed)
}
}
#[derive(Debug, Default)]
struct InlineState {
inlinees: HashMap<String, Tree>,
}
impl InlineState {
fn populate_inlinees(&mut self, book: &Book) -> Result<(), TransformError> {
for (name, net) in &book.nets {
if net.should_inline() {
// Detect cycles with tortoise and hare algorithm
let mut hare = &net.root;
let mut tortoise = &net.root;
// Whether or not the tortoise should take a step
let mut parity = false;
while let Tree::Ref { nam, .. } = hare {
let Some(net) = &book.nets.get(nam) else { break };
if net.should_inline() {
hare = &net.root;
} else {
break;
}
if parity {
let Tree::Ref { nam: tortoise_nam, .. } = tortoise else { unreachable!() };
if tortoise_nam == nam {
Err(TransformError::InfiniteRefCycle(nam.to_owned()))?;
}
tortoise = &book.nets[tortoise_nam].root;
}
parity = !parity;
}
self.inlinees.insert(name.to_owned(), hare.clone());
}
}
Ok(())
}
fn inline_into(&self, tree: &mut Tree) -> bool {
maybe_grow(|| {
let Tree::Ref { nam, .. } = &*tree else {
return tree.children_mut().map(|t| self.inline_into(t)).fold(false, bool::bitor);
};
if let Some(inlined) = self.inlinees.get(nam) {
*tree = inlined.clone();
true
} else {
false
}
})
}
}
impl Net {
fn should_inline(&self) -> bool {
self.redexes.is_empty() && self.root.children().next().is_none()
}
}

View File

@ -1,44 +0,0 @@
use std::collections::HashSet;
use crate::{
hvm::ast::{Book, Tree},
maybe_grow,
};
impl Book {
pub fn prune(&mut self, entrypoints: &[String]) {
let mut state = PruneState { book: self, unvisited: self.keys().map(|x| x.to_owned()).collect() };
for name in entrypoints {
state.visit_def(name);
}
let unvisited = state.unvisited;
for name in unvisited {
self.remove(&name);
}
}
}
#[derive(Debug)]
struct PruneState<'a> {
book: &'a Book,
unvisited: HashSet<String>,
}
impl<'a> PruneState<'a> {
fn visit_def(&mut self, name: &str) {
if self.unvisited.remove(name) {
for tree in self.book[name].trees() {
self.visit_tree(tree);
}
}
}
fn visit_tree(&mut self, tree: &Tree) {
maybe_grow(|| {
if let Tree::Ref { nam, .. } = tree {
self.visit_def(nam);
} else {
tree.children().for_each(|t| self.visit_tree(t));
}
})
}
}

View File

@ -1,6 +0,0 @@
pub(crate) mod array_vec;
pub mod bi_enum;
pub mod deref;
pub(crate) use bi_enum::*;
pub(crate) use deref::*;

View File

@ -1,29 +0,0 @@
use arrayvec::ArrayVec;
struct Assert<const COND: bool>;
trait IsTrue {}
impl IsTrue for Assert<true> {}
#[allow(private_bounds)]
pub(crate) fn from_array<T, const LEN: usize, const CAP: usize>(array: [T; LEN]) -> ArrayVec<T, CAP>
where
Assert<{ LEN <= CAP }>: IsTrue,
{
let mut vec = ArrayVec::new();
unsafe {
for el in array {
vec.push_unchecked(el)
}
}
vec
}
pub(crate) fn from_iter<T, const CAP: usize>(iter: impl IntoIterator<Item = T>) -> ArrayVec<T, CAP> {
let mut vec = ArrayVec::new();
for item in iter {
vec.push(item);
}
vec
}

View File

@ -1,54 +0,0 @@
/// Defines bi-directional mappings for a numeric enum.
macro_rules! bi_enum {
(
#[repr($uN:ident)]
$(#$attr:tt)*
$vis:vis enum $Ty:ident {
$($(#$var_addr:tt)* $Variant:ident = $value:literal),* $(,)?
}
) => {
#[repr($uN)] $(#$attr)* $vis enum $Ty { $($(#$var_addr)* $Variant = $value,)* }
impl TryFrom<$uN> for $Ty {
type Error = ();
fn try_from(value: $uN) -> Result<Self, Self::Error> {
Ok(match value { $($value => $Ty::$Variant,)* _ => Err(())?, })
}
}
impl $Ty {
#[allow(unused)]
pub unsafe fn from_unchecked(value: $uN) -> $Ty {
Self::try_from(value).unwrap_unchecked()
}
}
impl From<$Ty> for $uN {
fn from(value: $Ty) -> Self { value as Self }
}
};
(
#[repr($uN:ident)]
$(#$attr:tt)*
$vis:vis enum $Ty:ident {
$($(#$var_addr:tt)* $str:literal: $Variant:ident = $value:literal),* $(,)?
}
) => {
bi_enum! { #[repr($uN)] $(#$attr)* $vis enum $Ty { $($(#$var_addr)* $Variant = $value,)* } }
impl core::fmt::Display for $Ty {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(match self { $($Ty::$Variant => $str,)* })
}
}
impl core::str::FromStr for $Ty {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s { $($str => $Ty::$Variant,)* _ => Err(())?, })
}
}
};
}
pub(crate) use bi_enum;

View File

@ -1,17 +0,0 @@
macro_rules! deref {
($({$($gen:tt)*})? $ty:ty => self.$field:ident: $trg:ty) => {
impl $($($gen)*)? core::ops::Deref for $ty {
type Target = $trg;
fn deref(&self) -> &Self::Target {
&self.$field
}
}
impl $($($gen)*)? core::ops::DerefMut for $ty {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.$field
}
}
};
}
pub(crate) use deref;

View File

@ -1,30 +1,32 @@
#![feature(box_patterns)]
#![feature(let_chains)]
#![allow(incomplete_features, clippy::missing_safety_doc, clippy::new_ret_no_self)]
#![feature(generic_const_exprs)]
use crate::fun::{book_to_nets, net_to_term::net_to_term, term_to_net::Labels, Book, Ctx, Term};
use diagnostics::{Diagnostics, DiagnosticsConfig, ERR_INDENT_SIZE};
use hvm::{
add_recursive_priority::add_recursive_priority,
ast::Net,
check_net_size::{check_net_sizes, MAX_NET_SIZE},
mutual_recursion,
use crate::{
fun::{book_to_hvm, net_to_term::net_to_term, term_to_net::Labels, Book, Ctx, Term},
hvm::{
add_recursive_priority::add_recursive_priority,
check_net_size::{check_net_sizes, MAX_NET_SIZE},
display_hvm_book,
eta_reduce::eta_reduce_hvm_net,
inline::inline_hvm_book,
mutual_recursion,
prune::prune_hvm_book,
},
};
use net::hvmc_to_net::hvmc_to_net;
use std::str::FromStr;
use diagnostics::{Diagnostics, DiagnosticsConfig, ERR_INDENT_SIZE};
use net::hvm_to_net::hvm_to_net;
pub mod diagnostics;
pub mod fun;
pub mod hvm;
pub mod imp;
pub mod net;
mod utils;
pub use fun::load_book::load_file_to_book;
pub const ENTRY_POINT: &str = "main";
pub const HVM1_ENTRY_POINT: &str = "Main";
pub const HVM_OUTPUT_END_MARKER: &str = "Result: ";
pub fn check_book(
@ -45,20 +47,21 @@ pub fn compile_book(
) -> Result<CompileResult, Diagnostics> {
let mut diagnostics = desugar_book(book, opts.clone(), diagnostics_cfg, args)?;
let (mut hvm_book, labels) = book_to_nets(book, &mut diagnostics)?;
let (mut hvm_book, labels) = book_to_hvm(book, &mut diagnostics)?;
if opts.eta {
hvm_book.values_mut().for_each(hvm::ast::Net::eta_reduce);
hvm_book.defs.values_mut().for_each(eta_reduce_hvm_net);
}
mutual_recursion::check_cycles(&hvm_book, &mut diagnostics)?;
if opts.eta {
hvm_book.values_mut().for_each(hvm::ast::Net::eta_reduce);
hvm_book.defs.values_mut().for_each(eta_reduce_hvm_net);
}
if opts.inline {
diagnostics.start_pass();
if let Err(e) = hvm_book.inline() {
if let Err(e) = inline_hvm_book(&mut hvm_book) {
diagnostics.add_book_error(format!("During inlining:\n{:ERR_INDENT_SIZE$}{}", "", e));
}
diagnostics.fatal(())?;
@ -66,7 +69,7 @@ pub fn compile_book(
if opts.prune {
let prune_entrypoints = vec![book.hvmc_entrypoint().to_string()];
hvm_book.prune(&prune_entrypoints);
prune_hvm_book(&mut hvm_book, &prune_entrypoints);
}
if opts.check_net_size {
@ -75,7 +78,7 @@ pub fn compile_book(
add_recursive_priority(&mut hvm_book);
Ok(CompileResult { core_book: hvm_book, labels, diagnostics })
Ok(CompileResult { hvm_book, labels, diagnostics })
}
pub fn desugar_book(
@ -160,7 +163,7 @@ pub fn run_book(
args: Option<Vec<Term>>,
cmd: &str,
) -> Result<Option<(Term, String, Diagnostics)>, Diagnostics> {
let CompileResult { core_book, labels, diagnostics } =
let CompileResult { hvm_book: core_book, labels, diagnostics } =
compile_book(&mut book, compile_opts.clone(), diagnostics_cfg, args)?;
// TODO: Printing should be taken care by the cli module, but we'd
@ -177,14 +180,14 @@ pub fn run_book(
}
pub fn readback_hvm_net(
net: &Net,
net: &::hvm::ast::Net,
book: &Book,
labels: &Labels,
linear: bool,
adt_encoding: AdtEncoding,
) -> (Term, Diagnostics) {
let mut diags = Diagnostics::default();
let net = hvmc_to_net(net);
let net = hvm_to_net(net);
let mut term = net_to_term(&net, book, labels, linear, &mut diags);
term.expand_generated(book);
term.resugar_strings(adt_encoding);
@ -193,7 +196,7 @@ pub fn readback_hvm_net(
}
/// Runs an HVM book by invoking HVM as a subprocess.
fn run_hvm(book: &hvm::ast::Book, cmd: &str) -> Result<String, String> {
fn run_hvm(book: &::hvm::ast::Book, cmd: &str) -> Result<String, String> {
fn filter_hvm_output(
mut stream: impl std::io::Read + Send,
mut output: impl std::io::Write + Send,
@ -237,7 +240,7 @@ fn run_hvm(book: &hvm::ast::Book, cmd: &str) -> Result<String, String> {
}
let out_path = ".out.hvm";
std::fs::write(out_path, book.to_string()).map_err(|x| x.to_string())?;
std::fs::write(out_path, display_hvm_book(book).to_string()).map_err(|x| x.to_string())?;
let mut process = std::process::Command::new("hvm")
.arg(cmd)
.arg(out_path)
@ -258,14 +261,15 @@ fn run_hvm(book: &hvm::ast::Book, cmd: &str) -> Result<String, String> {
}
/// Reads the final output from HVM and separates the extra information.
fn parse_hvm_output(out: &str) -> Result<(Net, String), String> {
fn parse_hvm_output(out: &str) -> Result<(::hvm::ast::Net, String), String> {
let Some((result, stats)) = out.split_once('\n') else {
return Err(format!(
"Failed to parse result from HVM (unterminated result).\nOutput from HVM was:\n{:?}",
out
));
};
let Ok(net) = hvm::ast::Net::from_str(result) else {
let mut p = ::hvm::ast::CoreParser::new(result);
let Ok(net) = p.parse_net() else {
return Err(format!("Failed to parse result from HVM (invalid net).\nOutput from HVM was:\n{:?}", out));
};
Ok((net, stats.to_string()))
@ -401,7 +405,7 @@ impl std::fmt::Display for AdtEncoding {
pub struct CompileResult {
pub diagnostics: Diagnostics,
pub core_book: hvm::ast::Book,
pub hvm_book: ::hvm::ast::Book,
pub labels: Labels,
}

View File

@ -2,6 +2,7 @@ use bend::{
check_book, compile_book, desugar_book,
diagnostics::{Diagnostics, DiagnosticsConfig, Severity},
fun::{Book, Name},
hvm::display_hvm_book,
load_file_to_book, run_book, AdtEncoding, CompileOpts, OptLevel, RunOpts,
};
use clap::{Args, CommandFactory, Parser, Subcommand};
@ -230,7 +231,7 @@ pub enum WarningArgs {
fn main() -> ExitCode {
#[cfg(not(feature = "cli"))]
compile_error!("The 'cli' feature is needed for the hvm-lang cli");
compile_error!("The 'cli' feature is needed for the Bend cli");
let cli = Cli::parse();
@ -286,7 +287,7 @@ fn execute_cli_mode(mut cli: Cli) -> Result<(), Diagnostics> {
let compile_res = compile_book(&mut book, opts, diagnostics_cfg, None)?;
eprint!("{}", compile_res.diagnostics);
println!("{}", compile_res.core_book);
println!("{}", display_hvm_book(&compile_res.hvm_book));
}
Mode::GenC(GenArgs { comp_opts, warn_opts, path })
@ -298,7 +299,7 @@ fn execute_cli_mode(mut cli: Cli) -> Result<(), Diagnostics> {
let compile_res = compile_book(&mut book, opts, diagnostics_cfg, None)?;
let out_path = ".out.hvm";
std::fs::write(out_path, compile_res.core_book.to_string()).map_err(|x| x.to_string())?;
std::fs::write(out_path, compile_res.hvm_book.show()).map_err(|x| x.to_string())?;
let gen_fn = |out_path: &str| {
let mut process = std::process::Command::new("hvm");

View File

@ -1,17 +1,16 @@
use super::{INet, INode, INodes, NodeId, NodeKind::*, Port, SlotId, ROOT};
use crate::{
fun::Name,
hvm,
net::{CtrKind, NodeKind},
};
use hvm::ast::{Net, Tree};
pub fn hvmc_to_net(net: &Net) -> INet {
let inodes = hvmc_to_inodes(net);
pub fn hvm_to_net(net: &Net) -> INet {
let inodes = hvm_to_inodes(net);
inodes_to_inet(&inodes)
}
fn hvmc_to_inodes(net: &Net) -> INodes {
fn hvm_to_inodes(net: &Net) -> INodes {
let mut inodes = vec![];
let mut n_vars = 0;
let net_root = if let Tree::Var { nam } = &net.root { nam } else { "" };
@ -23,7 +22,7 @@ fn hvmc_to_inodes(net: &Net) -> INodes {
}
// Convert all the trees forming active pairs.
for (i, (_, tree1, tree2)) in net.redexes.iter().enumerate() {
for (i, (_, tree1, tree2)) in net.rbag.iter().enumerate() {
let tree_root = format!("a{i}");
let mut tree1 = tree_to_inodes(tree1, tree_root.clone(), net_root, &mut n_vars);
inodes.append(&mut tree1);
@ -47,55 +46,12 @@ fn tree_to_inodes(tree: &Tree, tree_root: String, net_root: &str, n_vars: &mut N
n_vars: &mut NodeId,
) -> String {
if let Tree::Var { nam } = subtree {
return if nam == net_root { "_".to_string() } else { nam.clone() };
}
if let Tree::Ctr { ports, .. } = subtree {
if ports.len() == 1 {
return process_node_subtree(&ports[0], net_root, subtrees, n_vars);
}
}
let var = new_var(n_vars);
subtrees.push((var.clone(), subtree));
var
}
fn process_ctr<'a>(
inodes: &mut Vec<INode>,
lab: u16,
ports: &'a [Tree],
net_root: &str,
principal: String,
subtrees: &mut Vec<(String, &'a Tree)>,
n_vars: &mut NodeId,
) {
fn process_sub_ctr<'a>(
inodes: &mut Vec<INode>,
lab: u16,
ports: &'a [Tree],
net_root: &str,
subtrees: &mut Vec<(String, &'a Tree)>,
n_vars: &mut NodeId,
) -> String {
if ports.len() == 1 {
process_node_subtree(&ports[0], net_root, subtrees, n_vars)
} else {
let principal = new_var(n_vars);
process_ctr(inodes, lab, ports, net_root, principal.clone(), subtrees, n_vars);
principal
}
}
if ports.is_empty() {
let inner = new_var(n_vars);
inodes.push(INode { kind: Era, ports: [principal, inner.clone(), inner] });
} else if ports.len() == 1 {
subtrees.push((principal, &ports[0]));
//
if nam == net_root { "_".to_string() } else { nam.clone() }
} else {
// build a 2-ary node
let kind = NodeKind::Ctr(CtrKind::from_lab(lab));
let rgt = process_sub_ctr(inodes, lab, &ports[1 ..], net_root, subtrees, n_vars);
let lft = process_node_subtree(&ports[0], net_root, subtrees, n_vars);
inodes.push(INode { kind, ports: [principal.clone(), lft.clone(), rgt.clone()] });
let var = new_var(n_vars);
subtrees.push((var.clone(), subtree));
var
}
}
@ -107,8 +63,17 @@ fn tree_to_inodes(tree: &Tree, tree_root: String, net_root: &str, n_vars: &mut N
let var = new_var(n_vars);
inodes.push(INode { kind: Era, ports: [subtree_root, var.clone(), var] });
}
Tree::Ctr { lab, ports } => {
process_ctr(&mut inodes, *lab, ports, net_root, subtree_root, &mut subtrees, n_vars)
Tree::Con { fst, snd } => {
let kind = NodeKind::Ctr(CtrKind::Con(None));
let fst = process_node_subtree(fst, net_root, &mut subtrees, n_vars);
let snd = process_node_subtree(snd, net_root, &mut subtrees, n_vars);
inodes.push(INode { kind, ports: [subtree_root, fst, snd] });
}
Tree::Dup { fst, snd } => {
let kind = NodeKind::Ctr(CtrKind::Dup(0));
let fst = process_node_subtree(fst, net_root, &mut subtrees, n_vars);
let snd = process_node_subtree(snd, net_root, &mut subtrees, n_vars);
inodes.push(INode { kind, ports: [subtree_root, fst, snd] });
}
Tree::Var { .. } => unreachable!(),
Tree::Ref { nam } => {
@ -117,24 +82,21 @@ fn tree_to_inodes(tree: &Tree, tree_root: String, net_root: &str, n_vars: &mut N
inodes.push(INode { kind, ports: [subtree_root, var.clone(), var] });
}
Tree::Num { val } => {
let kind = Num { val: *val };
let kind = Num { val: val.0 };
let var = new_var(n_vars);
inodes.push(INode { kind, ports: [subtree_root, var.clone(), var] });
}
Tree::Op { fst, snd } => {
Tree::Opr { fst, snd } => {
let kind = NodeKind::Opr;
let fst = process_node_subtree(fst, net_root, &mut subtrees, n_vars);
let snd = process_node_subtree(snd, net_root, &mut subtrees, n_vars);
inodes.push(INode { kind, ports: [subtree_root, fst, snd] });
}
Tree::Mat { zero, succ, out } => {
let kind = Mat;
let zero = process_node_subtree(zero, net_root, &mut subtrees, n_vars);
let succ = process_node_subtree(succ, net_root, &mut subtrees, n_vars);
let sel_var = new_var(n_vars);
inodes.push(INode { kind: NodeKind::Ctr(CtrKind::Con(None)), ports: [sel_var.clone(), zero, succ] });
let ret = process_node_subtree(out, net_root, &mut subtrees, n_vars);
inodes.push(INode { kind, ports: [subtree_root, sel_var, ret] });
Tree::Swi { fst, snd } => {
let kind = NodeKind::Mat;
let fst = process_node_subtree(fst, net_root, &mut subtrees, n_vars);
let snd = process_node_subtree(snd, net_root, &mut subtrees, n_vars);
inodes.push(INode { kind, ports: [subtree_root, fst, snd] });
}
}
}

View File

@ -1,4 +1,4 @@
pub mod hvmc_to_net;
pub mod hvm_to_net;
use crate::fun::Name;
pub type BendLab = u16;
@ -58,12 +58,6 @@ impl CtrKind {
CtrKind::Dup(_) => todo!("Tagged dups/sups not implemented for hvm32"),
}
}
fn from_lab(lab: u16) -> Self {
match lab {
0 => CtrKind::Con(None),
n => CtrKind::Dup(n - 1),
}
}
}
pub type NodeId = u64;

38
src/utils.rs Normal file
View File

@ -0,0 +1,38 @@
/// 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)]
enum $Iter<$($Variant),*> {
$($Variant { iter: $Variant }),*
}
impl<$($Variant),*> $Iter<$($Variant),*> {
$(
#[allow(non_snake_case)]
fn $Variant(iter: impl IntoIterator<IntoIter = $Variant>) -> Self {
$Iter::$Variant { iter: iter.into_iter() }
}
)*
}
impl<T, $($Variant: Iterator<Item = T>),*> Iterator for $Iter<$($Variant),*> {
type Item = T;
fn next(&mut self) -> Option<T> {
match self { $($Iter::$Variant { iter } => iter.next()),* }
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self { $($Iter::$Variant { iter } => iter.size_hint()),* }
}
}
impl<T, $($Variant: DoubleEndedIterator<Item = T>),*> DoubleEndedIterator for $Iter<$($Variant),*> {
fn next_back(&mut self) -> Option<T> {
match self { $($Iter::$Variant { iter } => iter.next_back()),* }
}
}
};
}

View File

@ -2,8 +2,8 @@ use bend::{
compile_book, desugar_book,
diagnostics::{Diagnostics, DiagnosticsConfig, Severity},
fun::{load_book::do_parse_book, net_to_term::net_to_term, term_to_net::Labels, Book, Ctx, Name, Term},
hvm,
net::hvmc_to_net::hvmc_to_net,
hvm::display_hvm_book,
net::hvm_to_net::hvm_to_net,
run_book, AdtEncoding, CompileOpts, RunOpts,
};
use insta::assert_snapshot;
@ -13,7 +13,6 @@ use std::{
fmt::Write,
io::Read,
path::{Path, PathBuf},
str::FromStr,
};
use stdext::function_name;
use walkdir::WalkDir;
@ -109,7 +108,7 @@ fn compile_file() {
let diagnostics_cfg = DiagnosticsConfig { unused_definition: Severity::Allow, ..Default::default() };
let res = compile_book(&mut book, compile_opts, diagnostics_cfg, None)?;
Ok(format!("{}{}", res.diagnostics, res.core_book))
Ok(format!("{}{}", res.diagnostics, display_hvm_book(&res.hvm_book)))
})
}
@ -125,7 +124,7 @@ fn compile_file_o_all() {
};
let res = compile_book(&mut book, opts, diagnostics_cfg, None)?;
Ok(format!("{}{}", res.diagnostics, res.core_book))
Ok(format!("{}{}", res.diagnostics, display_hvm_book(&res.hvm_book)))
})
}
@ -136,7 +135,7 @@ fn compile_file_o_no_all() {
let compile_opts = CompileOpts::default().set_no_all();
let diagnostics_cfg = DiagnosticsConfig::default();
let res = compile_book(&mut book, compile_opts, diagnostics_cfg, None)?;
Ok(format!("{}", res.core_book))
Ok(format!("{}", display_hvm_book(&res.hvm_book)))
})
}
@ -205,9 +204,10 @@ fn run_lazy() {
#[test]
fn readback_lnet() {
run_golden_test_dir(function_name!(), &|code, _| {
let net = hvm::ast::Net::from_str(code)?;
let mut p = hvm::ast::CoreParser::new(code);
let net = p.parse_net()?;
let book = Book::default();
let compat_net = hvmc_to_net(&net);
let compat_net = hvm_to_net(&net);
let mut diags = Diagnostics::default();
let term = net_to_term(&compat_net, &book, &Labels::default(), false, &mut diags);
Ok(format!("{}{}", diags, term))
@ -339,7 +339,7 @@ fn compile_entrypoint() {
book.entrypoint = Some(Name::new("foo"));
let diagnostics_cfg = DiagnosticsConfig { ..DiagnosticsConfig::new(Severity::Error, true) };
let res = compile_book(&mut book, CompileOpts::default(), diagnostics_cfg, None)?;
Ok(format!("{}{}", res.diagnostics, res.core_book))
Ok(format!("{}{}", res.diagnostics, display_hvm_book(&res.hvm_book)))
})
}
@ -386,7 +386,7 @@ fn mutual_recursion() {
let mut book = do_parse_book(code, path, Book::builtins())?;
let opts = CompileOpts { merge: true, ..CompileOpts::default() };
let res = compile_book(&mut book, opts, diagnostics_cfg, None)?;
Ok(format!("{}{}", res.diagnostics, res.core_book))
Ok(format!("{}{}", res.diagnostics, display_hvm_book(&res.hvm_book)))
})
}
@ -460,6 +460,6 @@ fn scott_triggers_unused() {
let diagnostics_cfg =
DiagnosticsConfig { unused_definition: Severity::Error, ..DiagnosticsConfig::default() };
let res = compile_book(&mut book, opts, diagnostics_cfg, None)?;
Ok(format!("{}{}", res.diagnostics, res.core_book))
Ok(format!("{}{}", res.diagnostics, display_hvm_book(&res.hvm_book)))
})
}

View File

@ -18,8 +18,8 @@ input_file: tests/golden_tests/cli/no_check_net_size.bend
@Gen.go__C1 = ({a d} ({$([*2] $([|1] e)) $([*2] b)} g))
& @Arr/Node ~ (c (f g))
&! @Gen.go ~ (a (b c))
&! @Gen.go ~ (d (e f))
&!@Gen.go ~ (a (b c))
&!@Gen.go ~ (d (e f))
@Main__C0 = a
& @Gen ~ (4 a)
@ -59,8 +59,8 @@ input_file: tests/golden_tests/cli/no_check_net_size.bend
@Merge__C5 = (* (b (e (a (d g)))))
& @Map_/Both ~ (c (f g))
&! @Merge ~ (a (b c))
&! @Merge ~ (d (e f))
&!@Merge ~ (a (b c))
&!@Merge ~ (d (e f))
@Merge__C6 = a
& @Map_/Both ~ a
@ -104,8 +104,8 @@ input_file: tests/golden_tests/cli/no_check_net_size.bend
@Reverse__C1 = (* (c (a e)))
& @Arr/Node ~ (b (d e))
&! @Reverse ~ (a b)
&! @Reverse ~ (c d)
&!@Reverse ~ (a b)
&!@Reverse ~ (c d)
@Reverse__C2 = (?((@Reverse__C0 @Reverse__C1) a) a)
@ -118,8 +118,8 @@ input_file: tests/golden_tests/cli/no_check_net_size.bend
@Sum = ((@Sum__C2 a) a)
@Sum__C0 = (* (a (b d)))
&! @Sum ~ (a $([+] $(c d)))
&! @Sum ~ (b c)
&!@Sum ~ (a $([+] $(c d)))
&!@Sum ~ (b c)
@Sum__C1 = (?(((a a) @Sum__C0) b) b)
@ -140,8 +140,8 @@ input_file: tests/golden_tests/cli/no_check_net_size.bend
@ToArr__C1 = (* (b (e ({$([*2] $([+1] d)) $([*2] $([+0] a))} g))))
& @Arr/Node ~ (c (f g))
&! @ToArr ~ (a (b c))
&! @ToArr ~ (d (e f))
&!@ToArr ~ (a (b c))
&!@ToArr ~ (d (e f))
@ToArr__C2 = (?((@ToArr__C0 @ToArr__C1) a) a)
@ -154,8 +154,8 @@ input_file: tests/golden_tests/cli/no_check_net_size.bend
@ToMap__C1 = (* (a (c e)))
& @Merge ~ (b (d e))
&! @ToMap ~ (a b)
&! @ToMap ~ (c d)
&!@ToMap ~ (a b)
&!@ToMap ~ (c d)
@ToMap__C2 = (?((@ToMap__C0 @ToMap__C1) a) a)

View File

@ -85,8 +85,8 @@ input_file: tests/golden_tests/compile_file/redex_order_recursive.bend
@Tree.flip__C0 = (c (a e))
& @Tree/node ~ (b (d e))
&! @Tree.flip ~ (a b)
&! @Tree.flip ~ (c d)
&!@Tree.flip ~ (a b)
&!@Tree.flip ~ (c d)
@Tree.flip__C1 = (* a)
& @Tree/leaf ~ a
@ -98,16 +98,16 @@ input_file: tests/golden_tests/compile_file/redex_order_recursive.bend
@Tree.height__C0 = (a (c f))
& $(e f) ~ [+1]
& @max ~ (b (d e))
&! @Tree.height ~ (a b)
&! @Tree.height ~ (c d)
&!@Tree.height ~ (a b)
&!@Tree.height ~ (c d)
@Tree.height__C1 = (?((@Tree.height__C0 (* (* 0))) a) a)
@Tree.leaves = ((@Tree.leaves__C1 a) a)
@Tree.leaves__C0 = (a (b d))
&! @Tree.leaves ~ (a $([+] $(c d)))
&! @Tree.leaves ~ (b c)
&!@Tree.leaves ~ (a $([+] $(c d)))
&!@Tree.leaves ~ (b c)
@Tree.leaves__C1 = (?((@Tree.leaves__C0 (* (* 1))) a) a)
@ -115,8 +115,8 @@ input_file: tests/golden_tests/compile_file/redex_order_recursive.bend
@Tree.map__C0 = (a (d ({b e} g)))
& @Tree/node ~ (c (f g))
&! @Tree.map ~ (a (b c))
&! @Tree.map ~ (d (e f))
&!@Tree.map ~ (a (b c))
&!@Tree.map ~ (d (e f))
@Tree.map__C1 = (* (a ((a b) c)))
& @Tree/leaf ~ (b c)
@ -127,8 +127,8 @@ input_file: tests/golden_tests/compile_file/redex_order_recursive.bend
@Tree.nodes__C0 = (a (b e))
& $(d e) ~ [+1]
&! @Tree.nodes ~ (a $([+] $(c d)))
&! @Tree.nodes ~ (b c)
&!@Tree.nodes ~ (a $([+] $(c d)))
&!@Tree.nodes ~ (b c)
@Tree.nodes__C1 = (?((@Tree.nodes__C0 (* (* 0))) a) a)
@ -151,8 +151,8 @@ input_file: tests/golden_tests/compile_file/redex_order_recursive.bend
@fold___C1 = (a (c e))
& @add ~ (b (d e))
&! @fold_ ~ (a b)
&! @fold_ ~ (c d)
&!@fold_ ~ (a b)
&!@fold_ ~ (c d)
@main = *

View File

@ -43,10 +43,10 @@ input_file: tests/golden_tests/compile_file_o_all/list_merge_sort.bend
@Merge__C0 = ({b {g l}} ({h q} ({(a (b c)) {e m}} ({a {d n}} ({f o} t)))))
& @If ~ (c (k (s t)))
& @List_/Cons ~ (d (j k))
&! @Merge ~ (e (f (i j)))
&!@Merge ~ (e (f (i j)))
& @List_/Cons ~ (g (h i))
& @List_/Cons ~ (l (r s))
&! @Merge ~ (m (p (q r)))
&!@Merge ~ (m (p (q r)))
& @List_/Cons ~ (n (o p))
@Merge__C1 = (* (* a))

View File

@ -18,7 +18,7 @@ It is lazy when it's an argument ('(x Foo)') or when it's used linearly ('let x
to:
'Foo = λa λb (b (λc λa (Foo a c)) (λa a) a)'
which is lifted to:
'Foo = λa λb (b Foo__C1 Foo_C2 a)'
'Foo = λa λb (b Foo__C1 Foo__C2 a)'
- Replace non-linear 'let' expressions with 'use' expressions. For example, change:
'Foo = λf let x = Foo; (f x x)'
to:
@ -27,7 +27,7 @@ It is lazy when it's an argument ('(x Foo)') or when it's used linearly ('let x
'Foo = λf (f Foo Foo)'
- If disabled, re-enable the default 'float-combinators' and 'linearize-matches' compiler options.
For more information, visit: https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md.
For more information, visit: https://github.com/HigherOrderCO/Bend/blob/main/docs/lazy-definitions.md.
To disable this check, use the "-Arecursion-cycle" compiler option.

View File

@ -18,7 +18,7 @@ It is lazy when it's an argument ('(x Foo)') or when it's used linearly ('let x
to:
'Foo = λa λb (b (λc λa (Foo a c)) (λa a) a)'
which is lifted to:
'Foo = λa λb (b Foo__C1 Foo_C2 a)'
'Foo = λa λb (b Foo__C1 Foo__C2 a)'
- Replace non-linear 'let' expressions with 'use' expressions. For example, change:
'Foo = λf let x = Foo; (f x x)'
to:
@ -27,7 +27,7 @@ It is lazy when it's an argument ('(x Foo)') or when it's used linearly ('let x
'Foo = λf (f Foo Foo)'
- If disabled, re-enable the default 'float-combinators' and 'linearize-matches' compiler options.
For more information, visit: https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md.
For more information, visit: https://github.com/HigherOrderCO/Bend/blob/main/docs/lazy-definitions.md.
To disable this check, use the "-Arecursion-cycle" compiler option.

View File

@ -23,7 +23,7 @@ It is lazy when it's an argument ('(x Foo)') or when it's used linearly ('let x
to:
'Foo = λa λb (b (λc λa (Foo a c)) (λa a) a)'
which is lifted to:
'Foo = λa λb (b Foo__C1 Foo_C2 a)'
'Foo = λa λb (b Foo__C1 Foo__C2 a)'
- Replace non-linear 'let' expressions with 'use' expressions. For example, change:
'Foo = λf let x = Foo; (f x x)'
to:
@ -32,5 +32,5 @@ It is lazy when it's an argument ('(x Foo)') or when it's used linearly ('let x
'Foo = λf (f Foo Foo)'
- If disabled, re-enable the default 'float-combinators' and 'linearize-matches' compiler options.
For more information, visit: https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md.
For more information, visit: https://github.com/HigherOrderCO/Bend/blob/main/docs/lazy-definitions.md.
To disable this check, use the "-Arecursion-cycle" compiler option.

View File

@ -19,7 +19,7 @@ It is lazy when it's an argument ('(x Foo)') or when it's used linearly ('let x
to:
'Foo = λa λb (b (λc λa (Foo a c)) (λa a) a)'
which is lifted to:
'Foo = λa λb (b Foo__C1 Foo_C2 a)'
'Foo = λa λb (b Foo__C1 Foo__C2 a)'
- Replace non-linear 'let' expressions with 'use' expressions. For example, change:
'Foo = λf let x = Foo; (f x x)'
to:
@ -28,5 +28,5 @@ It is lazy when it's an argument ('(x Foo)') or when it's used linearly ('let x
'Foo = λf (f Foo Foo)'
- If disabled, re-enable the default 'float-combinators' and 'linearize-matches' compiler options.
For more information, visit: https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md.
For more information, visit: https://github.com/HigherOrderCO/Bend/blob/main/docs/lazy-definitions.md.
To disable this check, use the "-Arecursion-cycle" compiler option.

View File

@ -19,7 +19,7 @@ It is lazy when it's an argument ('(x Foo)') or when it's used linearly ('let x
to:
'Foo = λa λb (b (λc λa (Foo a c)) (λa a) a)'
which is lifted to:
'Foo = λa λb (b Foo__C1 Foo_C2 a)'
'Foo = λa λb (b Foo__C1 Foo__C2 a)'
- Replace non-linear 'let' expressions with 'use' expressions. For example, change:
'Foo = λf let x = Foo; (f x x)'
to:
@ -28,5 +28,5 @@ It is lazy when it's an argument ('(x Foo)') or when it's used linearly ('let x
'Foo = λf (f Foo Foo)'
- If disabled, re-enable the default 'float-combinators' and 'linearize-matches' compiler options.
For more information, visit: https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md.
For more information, visit: https://github.com/HigherOrderCO/Bend/blob/main/docs/lazy-definitions.md.
To disable this check, use the "-Arecursion-cycle" compiler option.

View File

@ -18,7 +18,7 @@ It is lazy when it's an argument ('(x Foo)') or when it's used linearly ('let x
to:
'Foo = λa λb (b (λc λa (Foo a c)) (λa a) a)'
which is lifted to:
'Foo = λa λb (b Foo__C1 Foo_C2 a)'
'Foo = λa λb (b Foo__C1 Foo__C2 a)'
- Replace non-linear 'let' expressions with 'use' expressions. For example, change:
'Foo = λf let x = Foo; (f x x)'
to:
@ -27,5 +27,5 @@ It is lazy when it's an argument ('(x Foo)') or when it's used linearly ('let x
'Foo = λf (f Foo Foo)'
- If disabled, re-enable the default 'float-combinators' and 'linearize-matches' compiler options.
For more information, visit: https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md.
For more information, visit: https://github.com/HigherOrderCO/Bend/blob/main/docs/lazy-definitions.md.
To disable this check, use the "-Arecursion-cycle" compiler option.

View File

@ -21,7 +21,7 @@ It is lazy when it's an argument ('(x Foo)') or when it's used linearly ('let x
to:
'Foo = λa λb (b (λc λa (Foo a c)) (λa a) a)'
which is lifted to:
'Foo = λa λb (b Foo__C1 Foo_C2 a)'
'Foo = λa λb (b Foo__C1 Foo__C2 a)'
- Replace non-linear 'let' expressions with 'use' expressions. For example, change:
'Foo = λf let x = Foo; (f x x)'
to:
@ -30,5 +30,5 @@ It is lazy when it's an argument ('(x Foo)') or when it's used linearly ('let x
'Foo = λf (f Foo Foo)'
- If disabled, re-enable the default 'float-combinators' and 'linearize-matches' compiler options.
For more information, visit: https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md.
For more information, visit: https://github.com/HigherOrderCO/Bend/blob/main/docs/lazy-definitions.md.
To disable this check, use the "-Arecursion-cycle" compiler option.

View File

@ -21,7 +21,7 @@ It is lazy when it's an argument ('(x Foo)') or when it's used linearly ('let x
to:
'Foo = λa λb (b (λc λa (Foo a c)) (λa a) a)'
which is lifted to:
'Foo = λa λb (b Foo__C1 Foo_C2 a)'
'Foo = λa λb (b Foo__C1 Foo__C2 a)'
- Replace non-linear 'let' expressions with 'use' expressions. For example, change:
'Foo = λf let x = Foo; (f x x)'
to:
@ -30,5 +30,5 @@ It is lazy when it's an argument ('(x Foo)') or when it's used linearly ('let x
'Foo = λf (f Foo Foo)'
- If disabled, re-enable the default 'float-combinators' and 'linearize-matches' compiler options.
For more information, visit: https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md.
For more information, visit: https://github.com/HigherOrderCO/Bend/blob/main/docs/lazy-definitions.md.
To disable this check, use the "-Arecursion-cycle" compiler option.

View File

@ -18,7 +18,7 @@ It is lazy when it's an argument ('(x Foo)') or when it's used linearly ('let x
to:
'Foo = λa λb (b (λc λa (Foo a c)) (λa a) a)'
which is lifted to:
'Foo = λa λb (b Foo__C1 Foo_C2 a)'
'Foo = λa λb (b Foo__C1 Foo__C2 a)'
- Replace non-linear 'let' expressions with 'use' expressions. For example, change:
'Foo = λf let x = Foo; (f x x)'
to:
@ -27,5 +27,5 @@ It is lazy when it's an argument ('(x Foo)') or when it's used linearly ('let x
'Foo = λf (f Foo Foo)'
- If disabled, re-enable the default 'float-combinators' and 'linearize-matches' compiler options.
For more information, visit: https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md.
For more information, visit: https://github.com/HigherOrderCO/Bend/blob/main/docs/lazy-definitions.md.
To disable this check, use the "-Arecursion-cycle" compiler option.

View File

@ -18,7 +18,7 @@ It is lazy when it's an argument ('(x Foo)') or when it's used linearly ('let x
to:
'Foo = λa λb (b (λc λa (Foo a c)) (λa a) a)'
which is lifted to:
'Foo = λa λb (b Foo__C1 Foo_C2 a)'
'Foo = λa λb (b Foo__C1 Foo__C2 a)'
- Replace non-linear 'let' expressions with 'use' expressions. For example, change:
'Foo = λf let x = Foo; (f x x)'
to:
@ -27,5 +27,5 @@ It is lazy when it's an argument ('(x Foo)') or when it's used linearly ('let x
'Foo = λf (f Foo Foo)'
- If disabled, re-enable the default 'float-combinators' and 'linearize-matches' compiler options.
For more information, visit: https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md.
For more information, visit: https://github.com/HigherOrderCO/Bend/blob/main/docs/lazy-definitions.md.
To disable this check, use the "-Arecursion-cycle" compiler option.