mirror of
https://github.com/HigherOrderCO/Bend.git
synced 2024-08-17 07:40:48 +03:00
Add hvm ast and transformations
This commit is contained in:
parent
5d45e7008b
commit
c39a7614e3
54
Cargo.lock
generated
54
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"),
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
664
src/hvm/ast.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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
222
src/hvm/ops.rs
Normal 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
58
src/hvm/ops/num.rs
Normal 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
43
src/hvm/ops/word.rs
Normal 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
17
src/hvm/transform.rs
Normal 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}`"),
|
||||
}
|
||||
}
|
||||
}
|
168
src/hvm/transform/eta_reduce.rs
Normal file
168
src/hvm/transform/eta_reduce.rs
Normal 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]
|
||||
}
|
||||
}
|
||||
}
|
81
src/hvm/transform/inline.rs
Normal file
81
src/hvm/transform/inline.rs
Normal 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()
|
||||
}
|
||||
}
|
44
src/hvm/transform/prune.rs
Normal file
44
src/hvm/transform/prune.rs
Normal 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
6
src/hvm/util.rs
Normal 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
29
src/hvm/util/array_vec.rs
Normal 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
54
src/hvm/util/bi_enum.rs
Normal 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
17
src/hvm/util/deref.rs
Normal 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;
|
12
src/lib.rs
12
src/lib.rs
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user