Add hvm ast and transformations

This commit is contained in:
imaqtkatt 2024-05-21 10:52:24 -03:00
parent 5d45e7008b
commit c39a7614e3
24 changed files with 1470 additions and 111 deletions

54
Cargo.lock generated
View File

@ -2,15 +2,6 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "TSPL"
version = "0.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9423b1e6e2d6c0bbc03660f58f9c30f55359e13afea29432e6e767c0f7dc25"
dependencies = [
"highlight_error",
]
[[package]]
name = "TSPL"
version = "0.0.12"
@ -85,10 +76,10 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
name = "bend-lang"
version = "0.2.10"
dependencies = [
"TSPL 0.0.12",
"TSPL",
"arrayvec",
"clap",
"highlight_error",
"hvm-core",
"indexmap",
"insta",
"interner",
@ -212,21 +203,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e18805660d7b6b2e2b9f316a5099521b5998d5cba4dda11b5157a21aaef03"
[[package]]
name = "hvm-core"
version = "0.3.0-hvm32.compat.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34f45b2e7a27af7ccecc60ee9c28f8c536c19dac677a0ddd0a19c382eb1367ee"
dependencies = [
"TSPL 0.0.9",
"arrayvec",
"clap",
"nohash-hasher",
"parking_lot",
"stacker",
"thiserror",
]
[[package]]
name = "indexmap"
version = "2.2.6"
@ -304,12 +280,6 @@ dependencies = [
"scopeguard",
]
[[package]]
name = "nohash-hasher"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
[[package]]
name = "parking_lot"
version = "0.12.2"
@ -432,26 +402,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"

View File

@ -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-core = "=0.3.0-hvm32.compat.4"
indexmap = "2.2.3"
interner = "0.2.1"
itertools = "0.11.0"

View File

@ -1,9 +1,7 @@
use crate::{
diagnostics::{Diagnostics, DiagnosticsConfig},
fun::builtins::*,
maybe_grow, ENTRY_POINT,
diagnostics::{Diagnostics, DiagnosticsConfig}, fun::builtins::*, hvm::{self, ast::get_typ}, maybe_grow, ENTRY_POINT
};
use hvmc::ast::get_typ;
// use hvmc::ast::get_typ;
use indexmap::{IndexMap, IndexSet};
use interner::global::{GlobalPool, GlobalString};
use itertools::Itertools;
@ -901,17 +899,17 @@ impl Num {
pub fn to_bits(&self) -> u32 {
match self {
Num::U24(val) => hvmc::ast::new_u24(*val),
Num::I24(val) => hvmc::ast::new_i24(*val),
Num::F24(val) => hvmc::ast::new_f24(*val),
Num::U24(val) => hvm::ast::new_u24(*val),
Num::I24(val) => hvm::ast::new_i24(*val),
Num::F24(val) => hvm::ast::new_f24(*val),
}
}
pub fn from_bits(bits: u32) -> Self {
match get_typ(bits) {
hvmc::ast::TY_U24 => Num::U24(hvmc::ast::get_u24(bits)),
hvmc::ast::TY_I24 => Num::I24(hvmc::ast::get_i24(bits)),
hvmc::ast::TY_F24 => Num::F24(hvmc::ast::get_f24(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)),
_ => unreachable!("Invalid Num bits"),
}
}

View File

@ -1,10 +1,7 @@
use hvmc::ast::{get_f24, get_i24, get_typ, get_u24};
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},
maybe_grow,
net::{CtrKind, INet, NodeId, NodeKind, Port, SlotId, ROOT},
diagnostics::{DiagnosticOrigin, Diagnostics, Severity}, fun::{term_to_net::Labels, Book, FanKind, Name, Op, Pattern, Tag, Term}, hvm, maybe_grow, net::{CtrKind, INet, NodeId, NodeKind, Port, SlotId, ROOT}
};
use std::collections::{BTreeSet, HashMap, HashSet};
@ -224,7 +221,7 @@ 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) != hvmc::ast::TY_SYM {
if get_typ(val) != hvm::ast::TY_SYM {
self.error(ReadbackError::InvalidNumericOp);
return Term::Err;
}
@ -479,10 +476,10 @@ impl Term {
fn num_from_bits_with_type(val: u32, typ: u32) -> Term {
match get_typ(typ) {
// No type information, assume u24 by default
hvmc::ast::TY_SYM => Term::Num { val: Num::U24(get_u24(val)) },
hvmc::ast::TY_U24 => Term::Num { val: Num::U24(get_u24(val)) },
hvmc::ast::TY_I24 => Term::Num { val: Num::I24(get_i24(val)) },
hvmc::ast::TY_F24 => Term::Num { val: Num::F24(get_f24(val)) },
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)) },
_ => Term::Err,
}
}

View File

@ -1,7 +1,7 @@
use crate::{
diagnostics::Diagnostics,
fun::{Book, Name, Pattern, Tag, Term},
maybe_grow,
hvm, maybe_grow,
net::CtrKind::{self, *},
};
use std::{
@ -9,7 +9,7 @@ use std::{
ops::{Index, IndexMut},
};
use hvmc::ast::{Net, Tree};
use hvm::ast::{Net, Tree};
use loaned::LoanedMut;
use super::{num_to_name, FanKind, Op};
@ -17,10 +17,10 @@ use super::{num_to_name, FanKind, Op};
#[derive(Debug, Clone)]
pub struct ViciousCycleErr;
pub fn book_to_nets(book: &Book, diags: &mut Diagnostics) -> Result<(hvmc::ast::Book, Labels), Diagnostics> {
pub fn book_to_nets(book: &Book, diags: &mut Diagnostics) -> Result<(hvm::ast::Book, Labels), Diagnostics> {
diags.start_pass();
let mut hvmc = hvmc::ast::Book::default();
let mut hvmc = hvm::ast::Book::default();
let mut labels = Labels::default();
let main = book.entrypoint.as_ref().unwrap();
@ -158,7 +158,7 @@ impl<'t, 'l> EncodeTermState<'t, 'l> {
}
Term::Let { pat, val, nxt } => {
// Dups/tup eliminators are not actually scoped like other terms.
// They are depended on
// They are depended on
self.lets.push((pat, val));
self.encode_term(nxt, up);
}
@ -184,7 +184,7 @@ impl<'t, 'l> EncodeTermState<'t, 'l> {
(fst, Term::Num { val }) => {
if [Op::AND, Op::OR, Op::XOR].contains(opr) {
// no flip and no partial application
let opr_val = hvmc::ast::new_sym(opr.to_native_tag());
let opr_val = hvm::ast::new_sym(opr.to_native_tag());
let oper = Place::Tree(LoanedMut::new(Tree::Num { val: opr_val }));
let node1 = self.new_opr();
self.encode_term(fst, node1.0);
@ -196,7 +196,7 @@ impl<'t, 'l> EncodeTermState<'t, 'l> {
} else {
// flip
let val = val.to_bits();
let val = (val & !0x1F) | hvmc::ast::flip_sym(opr.to_native_tag());
let val = (val & !0x1F) | hvm::ast::flip_sym(opr.to_native_tag());
let snd = Place::Tree(LoanedMut::new(Tree::Num { val }));
let node = self.new_opr();
self.encode_term(fst, node.0);
@ -206,7 +206,7 @@ impl<'t, 'l> EncodeTermState<'t, 'l> {
}
// Don't partially apply
(fst, snd) => {
let opr_val = hvmc::ast::new_sym(opr.to_native_tag());
let opr_val = hvm::ast::new_sym(opr.to_native_tag());
let oper = Place::Tree(LoanedMut::new(Tree::Num { val: opr_val }));
let node1 = self.new_opr();
self.encode_term(fst, node1.0);
@ -423,24 +423,24 @@ fn hole<T: Default>() -> T {
impl Op {
fn to_native_tag(self) -> u32 {
match self {
Op::ADD => hvmc::ast::OP_ADD,
Op::SUB => hvmc::ast::OP_SUB,
Op::MUL => hvmc::ast::OP_MUL,
Op::DIV => hvmc::ast::OP_DIV,
Op::REM => hvmc::ast::OP_REM,
Op::EQ => hvmc::ast::OP_EQ,
Op::NEQ => hvmc::ast::OP_NEQ,
Op::LT => hvmc::ast::OP_LT,
Op::GT => hvmc::ast::OP_GT,
Op::AND => hvmc::ast::OP_AND,
Op::OR => hvmc::ast::OP_OR,
Op::XOR => hvmc::ast::OP_XOR,
Op::SHL => hvmc::ast::OP_SHL,
Op::SHR => hvmc::ast::OP_SHR,
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::ATN => hvmc::ast::OP_AND,
Op::LOG => hvmc::ast::OP_OR,
Op::POW => hvmc::ast::OP_XOR,
Op::ATN => hvm::ast::OP_AND,
Op::LOG => hvm::ast::OP_OR,
Op::POW => hvm::ast::OP_XOR,
}
}
}

View File

@ -1,5 +1,7 @@
use crate::maybe_grow;
use hvmc::ast::{Book, Net, Tree};
use crate::{
hvm::ast::{Book, Net, Tree},
maybe_grow,
};
use std::collections::{HashMap, HashSet};
pub fn add_recursive_priority(book: &mut Book) {

664
src/hvm/ast.rs Normal file
View File

@ -0,0 +1,664 @@
//! 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
/// dereferencable 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 {
return (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 {
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
return 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() {
return self.parse_numb_sym();
// Parses numbers (U24,I24,F24)
} else {
return 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,5 +1,5 @@
use super::ast::Book;
use crate::{diagnostics::Diagnostics, fun::Name};
use hvmc::ast::Book;
pub const MAX_NET_SIZE: usize = 64;
@ -20,8 +20,8 @@ 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 hvmc::ast::Net) -> usize {
let mut visit: Vec<&'l hvmc::ast::Tree> = vec![&net.root];
pub fn count_nodes<'l>(net: &'l super::ast::Net) -> usize {
let mut visit: Vec<&'l super::ast::Tree> = vec![&net.root];
let mut count = 0usize;
for (_, l, r) in &net.redexes {
visit.push(l);

View File

@ -1,3 +1,7 @@
pub mod add_recursive_priority;
pub mod ast;
pub mod check_net_size;
pub mod mutual_recursion;
pub mod ops;
pub mod transform;
pub mod util;

View File

@ -3,10 +3,11 @@ use crate::{
fun::transform::definition_merge::MERGE_SEPARATOR,
maybe_grow,
};
use hvmc::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>;

222
src/hvm/ops.rs Normal file
View File

@ -0,0 +1,222 @@
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),
// }

58
src/hvm/ops/num.rs Normal file
View File

@ -0,0 +1,58 @@
#[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
}
}

43
src/hvm/ops/word.rs Normal file
View File

@ -0,0 +1,43 @@
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
}
}

17
src/hvm/transform.rs Normal file
View File

@ -0,0 +1,17 @@
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

@ -0,0 +1,168 @@
//! Carries out simple eta-reduction, to reduce the amount of rewrites at
//! runtime.
//!
//! ### Eta-equivalence
//!
//! In interaction combinators, there are some nets that are equivalent and
//! have no observable difference
//!
//! ![Image of eta-equivalence](https://i.postimg.cc/XYVxdMFW/image.png)
//!
//! This module implements the eta-equivalence rule at the top-left of the image
//! above
//!
//! ```txt
//! /|-, ,-|\ eta_reduce
//! ---| | X | |-- ~~~~~~~~~~~~> -------------
//! \|-' '-|/
//! ```
//!
//! In hvm-core's AST representation, this reduction looks like this
//!
//! ```txt
//! {lab x y} ... {lab x y} ~~~~~~~~> x ..... x
//! ```
//!
//! Essentially, both occurrences of the same constructor are replaced by a
//! variable.
//!
//! ### The algorithm
//!
//! The code uses a two-pass O(n) algorithm, where `n` is the amount of nodes
//! in the AST
//!
//! In the first pass, a node-list is built out of an ordered traversal of the
//! AST. Crucially, the node list stores variable offsets instead of the
//! variable's names Since the AST's order is consistent, the ordering of nodes
//! in the node list can be reproduced with a traversal.
//!
//! This means that each occurrence of a variable is encoded with the offset in
//! the node-list to the _other_ occurrence of the variable.
//!
//! For example, if we start with the net: `[(x y) (x y)]`
//!
//! The resulting node list will look like this:
//!
//! `[Ctr(1), Ctr(0), Var(3), Var(3), Ctr(0), Var(-3), Var(-3)]`
//!
//! The second pass uses the node list to find repeated constructors. If a
//! constructor's children are both variables with the same offset, then we
//! lookup that offset relative to the constructor. If it is equal to the first
//! constructor, it means both of them are equal and they can be replaced with a
//! variable.
//!
//! The pass also reduces subnets such as `(* *) -> *`
use crate::hvm::ast::{Net, Tree};
use core::ops::RangeFrom;
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);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum NodeType {
Ctr(u16),
Var(isize),
Num(u32),
Era,
Other,
Hole,
}
#[derive(Default)]
struct Phase1<'a> {
vars: HashMap<&'a str, usize>,
nodes: Vec<NodeType>,
}
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::Var { nam } => {
if let Some(i) = self.vars.get(&**nam) {
let j = self.nodes.len() as isize;
self.nodes.push(NodeType::Var(*i as isize - j));
self.nodes[*i] = NodeType::Var(j - *i as isize);
} else {
self.vars.insert(nam, self.nodes.len());
self.nodes.push(NodeType::Hole);
}
}
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() {
self.walk_tree(i);
}
}
}
}
}
struct Phase2 {
nodes: Vec<NodeType>,
index: RangeFrom<usize>,
}
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 | NodeType::Num(_) => true,
_ => false,
};
if reducible {
ports.pop();
return a;
}
}
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
} else {
let index = self.index.next().unwrap();
for i in tree.children_mut() {
self.reduce_tree(i);
}
self.nodes[index]
}
}
}

View File

@ -0,0 +1,81 @@
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

@ -0,0 +1,44 @@
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));
}
})
}
}

6
src/hvm/util.rs Normal file
View File

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

29
src/hvm/util/array_vec.rs Normal file
View File

@ -0,0 +1,29 @@
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
}

54
src/hvm/util/bi_enum.rs Normal file
View File

@ -0,0 +1,54 @@
/// 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;

17
src/hvm/util/deref.rs Normal file
View File

@ -0,0 +1,17 @@
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,14 +1,16 @@
#![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 hvmc::ast::Net;
use net::hvmc_to_net::hvmc_to_net;
use std::{process::Output, str::FromStr};
@ -44,12 +46,12 @@ pub fn compile_book(
let (mut hvm_book, labels) = book_to_nets(book, &mut diagnostics)?;
if opts.eta {
hvm_book.values_mut().for_each(Net::eta_reduce);
hvm_book.values_mut().for_each(hvm::ast::Net::eta_reduce);
}
mutual_recursion::check_cycles(&hvm_book, &mut diagnostics)?;
if opts.eta {
hvm_book.values_mut().for_each(Net::eta_reduce);
hvm_book.values_mut().for_each(hvm::ast::Net::eta_reduce);
}
if opts.inline {
@ -200,7 +202,7 @@ pub fn run_book_with_fn(
.into(),
);
};
let net = match hvmc::ast::Net::from_str(result) {
let net = match hvm::ast::Net::from_str(result) {
Ok(net) => net,
Err(e) => {
return Err(
@ -364,7 +366,7 @@ impl std::fmt::Display for AdtEncoding {
pub struct CompileResult {
pub diagnostics: Diagnostics,
pub core_book: hvmc::ast::Book,
pub core_book: hvm::ast::Book,
pub labels: Labels,
}

View File

@ -1,9 +1,10 @@
use super::{INet, INode, INodes, NodeId, NodeKind::*, Port, SlotId, ROOT};
use crate::{
fun::Name,
hvm,
net::{CtrKind, NodeKind},
};
use hvmc::ast::{Net, Tree};
use hvm::ast::{Net, Tree};
pub fn hvmc_to_net(net: &Net) -> INet {
let inodes = hvmc_to_inodes(net);

View File

@ -2,6 +2,7 @@ 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,
run_book_with_fn, AdtEncoding, CompileOpts, RunOpts,
};
@ -204,7 +205,7 @@ fn run_lazy() {
#[test]
fn readback_lnet() {
run_golden_test_dir(function_name!(), &|code, _| {
let net = hvmc::ast::Net::from_str(code)?;
let net = hvm::ast::Net::from_str(code)?;
let book = Book::default();
let compat_net = hvmc_to_net(&net);
let mut diags = Diagnostics::default();