Merge branch 'HigherOrderCO:main' into main

This commit is contained in:
Sipher 2024-05-27 11:05:18 -03:00 committed by GitHub
commit 28eb877f7b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
78 changed files with 1646 additions and 2330 deletions

View File

@ -13,7 +13,7 @@ jobs:
timeout-minutes: 10
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@nightly
- uses: dtolnay/rust-toolchain@stable
- uses: actions/cache@v2
with:
path: |
@ -27,7 +27,7 @@ jobs:
timeout-minutes: 10
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@nightly
- uses: dtolnay/rust-toolchain@stable
- uses: actions/cache@v2
with:
path: |
@ -42,7 +42,7 @@ jobs:
timeout-minutes: 10
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@nightly
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: actions/cache@v2
@ -58,7 +58,7 @@ jobs:
timeout-minutes: 10
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@nightly
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: cargo fmt --check

View File

@ -1,12 +1,8 @@
edition = "2021"
version = "Two"
max_width = 110
# won't add \r\n on windows machines, better on diffs
newline_style = "Unix"
use_small_heuristics = "Max"
tab_spaces = 2
imports_granularity = "Crate"
use_field_init_shorthand = true
use_try_shorthand = true
spaces_around_ranges = true
overflow_delimited_expr = true

57
Cargo.lock generated
View File

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

View File

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

View File

@ -1,4 +1,6 @@
# Known Issues
# Known Issues and Frequently Asked Questions
## Installation and Setup
### Can I run this on Windows?
- We're still working on the windows support, for the moment, please use [WSL2](https://learn.microsoft.com/en-us/windows/wsl/install).
@ -9,9 +11,6 @@
### I'm getting a `Failed to launch kernels (error code invalid argument)!` error.
- The current iteration of the `hvm.cu` was written with the RTX 4090 in mind, and won't work on older GPUs, since they contain about half of the newer GPUs shared memory, for better understanding please refer to [HVM#283](https://github.com/HigherOrderCO/HVM/issues/283). We are working on support for older GPUs and will release it soon.
### Can I run this on AMD/Intel/Apple GPUs?
- We plan on adding support to many other GPUs as soon as the CUDA version is sufficiently stable.
### I get "Command `bend` not found" after installing, what do I do?
- If you are on Unix system (or WSL) then most likely bend did not add itself to the PATH variable in your rc file. To remedy this:
- Determine if you are using bash or zsh (check for presence of `~/.bashrc` or `~/.zshrc`)
@ -21,10 +20,25 @@
- Try running `bend` once more - it should work now!
### I got an error when installing HVM on Linux
- If the error contains anything regarding `ccbin`, please refer to [HVM#291](https://github.com/HigherOrderCO/HVM/issues/291)
- If the error happens when compiling the CUDA runtime or contains anything regarding `ccbin`, please refer to [HVM#291](https://github.com/HigherOrderCO/HVM/issues/291).
- If the error contains anything regarding `libc` missing, please refer to [HVM#355](https://github.com/HigherOrderCO/Bend/issues/355)
### Can I run this on AMD/Intel/Apple GPUs?
- We plan on adding support to many other GPUs as soon as the CUDA version is sufficiently stable.
### What GPUs are supported?
GPUs with >=96KB L1 cache per SM *should* work. This includes the following:
| GPU | Tested? |
|-----------------------------|---------|
| RTX 4090 | Yes |
| All Other Desktop 40 Series | No |
| All Desktop 30 Series | No |
| All Mobile 40 Series | No |
| All Mobile 30 Series | No |
## Using Bend
### How do I use IO?
- IO is still being developed and is expected to come soon.

View File

@ -172,7 +172,6 @@ def MyTree.map_sum(x):
This allows `fold` to be a very powerful and generic tool that can be used to implement most pure data transformations.
### Some caveats and limitations
_Attention_: Note that despite the ADT syntax sugars, Bend is an _untyped_ language and the compiler will not stop you from using values incorrectly, which can lead to very unexpected results.
@ -287,7 +286,7 @@ switch x = 4:
Bend has Lists and Strings, which support Unicode characters.
```rs
```py
def main:
return ["You: Hello, 🌎", "🌎: Hello, user"]
```
@ -295,12 +294,12 @@ def main:
A string is desugared to a String data type containing two constructors, `String/Cons` and `String/Nil`.
List also becomes a type with two constructors, `List/Cons` and `List/Nil`.
```rs
```py
# When you write this
def StrEx:
"Hello"
return "Hello"
def ids:
[1, 2, 3]
return [1, 2, 3]
# The compiler converts it to this
def StrEx:
@ -319,15 +318,62 @@ type List:
Characters are delimited by `'` `'` and support Unicode escape sequences. They are encoded as a U24 with the unicode codepoint as their value.
```
```py
# These two are equivalent
def chars:
['A', '\u{4242}', '🌎']
return ['A', '\u{4242}', '🌎']
def chars2:
[65, 0x4242, 0x1F30E]
return [65, 0x4242, 0x1F30E]
```
Bend has a built-in binary tree Map data structure where the key is a `u24` value, meaning you can use numbers, characters, and symbols as keys.
Maps are delimited by `{` `}` and its entries are separated by commas. A key-value entry in a map is denoted using a colon `:`. For example:
```py
{ 42: [4, 2] } # 42 is the key and [4, 2] is the value
```
A Map is desugared to a Map data type containing two constructors `Map/Leaf` and `Map/Node`.
```py
# When you write this
def empty_map:
return {}
def init_map:
return { 1: "one", 2: "two", `blue`: 0x0000FF }
def main:
map = init_map
one = map[1] # map getter syntax
map[0] = "zero" # map setter syntax
return one
# The compiler converts it to this
def empty_map():
return Map/Leaf
def init_map():
map = Map/set(Map/Leaf, 1, "one")
map = Map/set(map, 2, "two")
map = Map/set(map, `blue`, 0x0000FF)
return map
def main():
map = init_map
(one, map) = Map/get(map, 1)
map = Map/set(map, 0, "zero")
return one
# The builtin Map type definition
type Map:
Node { value, ~left, ~right }
Leaf
```
Notice that the getter and setter syntax induces an order on things using the map, since every get or set operation depends on the value of the previous map.
### Mixing syntaxes
@ -368,7 +414,7 @@ Other features are described in the following documentation files:
- 📗 Data types: [Defining data types](docs/defining-data-types.md)
- 📗 Pattern matching: [Pattern matching](docs/pattern-matching.md)
- 📗 Native numbers and operations: [Native numbers](docs/native-numbers.md)
- 📗 Builtin definitions: *Documentation coming soon*
- 📗 Builtin definitions: _Documentation coming soon_
- 📗 CLI arguments: [CLI arguments](docs/cli-arguments.md)
- 📙 Duplications and superpositions: [Dups and sups](docs/dups-and-sups.md)
- 📙 Scopeless lambdas: [Using scopeless lambdas](docs/using-scopeless-lambdas.md)

View File

@ -23,11 +23,11 @@ you just want to dive straight into action - this guide is for you. Let's go!
Installation
------------
To use Bend, first, install [Rust nightly](https://www.oreilly.com/library/view/rust-programming-by/9781788390637/e07dc768-de29-482e-804b-0274b4bef418.xhtml). Then, install HVM2 and Bend itself with:
To use Bend, first, install [Rust](https://www.rust-lang.org/tools/install). Then, install HVM2 and Bend itself with:
```
cargo +nightly install hvm
cargo +nightly install bend-lang
cargo install hvm
cargo install bend-lang
```
To test if it worked, type:

View File

@ -23,9 +23,9 @@ A Quick Demo
> Currently not working on Windows, please use [WSL2](https://learn.microsoft.com/en-us/windows/wsl/install) as a workaround.
> If you're having issues or have a question about Bend, please first read the [Known Issues](https://github.com/HigherOrderCO/Bend/blob/main/KNOWN_ISSUES.md) page and check if your question has already been addressed.
> If you're having issues or have a question about Bend, please first read the [FAQ](https://github.com/HigherOrderCO/Bend/blob/main/KNOWN_ISSUES.md) page and check if your question has already been addressed.
First, install [Rust nightly](https://www.oreilly.com/library/view/rust-programming-by/9781788390637/e07dc768-de29-482e-804b-0274b4bef418.xhtml).
First, install [Rust](https://www.rust-lang.org/tools/install).
If you want to use the C runtime, install a C compiler (like GCC or Clang).
If you want to use the CUDA runtime, install the CUDA toolkit (CUDA and `nvcc`) version 12.x.
@ -35,8 +35,8 @@ If you want to use the CUDA runtime, install the CUDA toolkit (CUDA and `nvcc`)
Then, install both HVM2 and Bend with:
```sh
cargo +nightly install hvm
cargo +nightly install bend-lang
cargo install hvm
cargo install bend-lang
```
Finally, write some Bend file, and run it with one of these commands:

View File

@ -32,6 +32,8 @@
"dref",
"dups",
"effectful",
"elif",
"elifs",
"foldl",
"hasher",
"hexdigit",
@ -83,6 +85,7 @@
"Pythonish",
"quadtree",
"quadtrees",
"rbag",
"readback",
"recursively",
"redex",
@ -119,10 +122,7 @@
"walkdir",
"wopts"
],
"files": [
"**/*.rs",
"**/*.md"
],
"files": ["**/*.rs", "**/*.md"],
"ignoreRegExpList": [
"HexValues",
"/λ/g",

View File

@ -16,7 +16,7 @@ def main(x1, x2, x3):
return MainBody(x1 x2 x3)
# Calling with `bend run <file> arg1 arg2 arg3 argN`, it becomes (in the "fun" syntax):
main = (x1 λx2 λx3 (MainBody x1 x2 x3) arg1 arg2 arg3 argN)
main = (λx1 λx2 λx3 (MainBody x1 x2 x3) arg1 arg2 arg3 argN)
```
There are no restrictions on the number of arguments passed to the program.

View File

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

View File

@ -77,24 +77,24 @@ These are the currently available operations:
Operation | Description | Accepted types | Return type
----------|-------------|----------------|------------
+ | Addition | U24, I24, F24 | Same as arguments
- | Subtraction | U24, I24, F24 | Same as arguments
* | Multiplication | U24, I24, F24 | Same as arguments
/ | Division | U24, I24, F24 | Same as arguments
% | Modulo | U24, I24, F24 | Same as arguments
== | Equality | U24, I24, F24 | U24
!= | Inequality | U24, I24, F24 | U24
< | Less than | U24, I24, F24 | U24
\+ | Addition | U24, I24, F24 | Same as arguments
\- | Subtraction | U24, I24, F24 | Same as arguments
\* | Multiplication | U24, I24, F24 | Same as arguments
\/ | Division | U24, I24, F24 | Same as arguments
\% | Modulo | U24, I24, F24 | Same as arguments
\== | Equality | U24, I24, F24 | U24
\!= | Inequality | U24, I24, F24 | U24
\< | Less than | U24, I24, F24 | U24
\> | Greater than | U24, I24, F24 | U24
& | Bitwise and | U24, I24 | Same as arguments
| | Bitwise or | U24, I24 | Same as arguments
^ | Bitwise xor | U24, I24 | Same as arguments
** | Exponentiation | F24 | F24
\& | Bitwise and | U24, I24 | Same as arguments
\| | Bitwise or | U24, I24 | Same as arguments
\^ | Bitwise xor | U24, I24 | Same as arguments
\** | Exponentiation | F24 | F24
### Pattern matching
HVM-lang also includes a `switch` syntax for pattern-matching U24 numbers.
Bend also includes a `switch` syntax for pattern-matching U24 numbers.
```rs
Number.to_church = λn λf λx

View File

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

View File

@ -193,6 +193,20 @@ A branching statement where `else` is mandatory.
The condition must return a `u24` number, where 0 will run the `else` branch and any other value will return the first one.
It is possible to make if-chains using `elif`:
```python
if condition1:
return 0
elif condition2:
return 1
elif condition3:
return 2
else:
return 3
```
The conditions are evaluated in order, one by one, stopping at the first successful case.
### Switch
```python

View File

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

View File

@ -1,4 +0,0 @@
[toolchain]
profile = "minimal"
channel = "nightly-2024-01-27"
components = ["rustfmt", "clippy"]

View File

@ -145,7 +145,11 @@ impl Diagnostics {
/// Returning all the current information as a `Err(Info)`, replacing `&mut self` with an empty one.
/// Otherwise, returns the given arg as an `Ok(T)`.
pub fn fatal<T>(&mut self, t: T) -> Result<T, Diagnostics> {
if self.err_counter == 0 { Ok(t) } else { Err(std::mem::take(self)) }
if self.err_counter == 0 {
Ok(t)
} else {
Err(std::mem::take(self))
}
}
/// Returns a Display that prints the diagnostics with one of the given severities.
@ -213,10 +217,10 @@ impl Display for Diagnostics {
impl From<String> for Diagnostics {
fn from(value: String) -> Self {
Self {
diagnostics: BTreeMap::from_iter([(DiagnosticOrigin::Book, vec![Diagnostic {
message: value,
severity: Severity::Error,
}])]),
diagnostics: BTreeMap::from_iter([(
DiagnosticOrigin::Book,
vec![Diagnostic { message: value, severity: Severity::Error }],
)]),
..Default::default()
}
}

View File

@ -66,7 +66,7 @@ impl Term {
}
pub fn encode_nat(val: u32) -> Term {
(0 .. val).fold(Term::r#ref(NAT_ZERO), |acc, _| Term::app(Term::r#ref(NAT_SUCC), acc))
(0..val).fold(Term::r#ref(NAT_ZERO), |acc, _| Term::app(Term::r#ref(NAT_SUCC), acc))
}
}

View File

@ -52,7 +52,11 @@ impl Ctx<'_> {
}
fn validate_entry_point(entry: &Definition) -> Result<Name, EntryErr> {
if entry.rules.len() > 1 { Err(EntryErr::MultipleRules) } else { Ok(entry.name.clone()) }
if entry.rules.len() > 1 {
Err(EntryErr::MultipleRules)
} else {
Ok(entry.name.clone())
}
}
impl Book {

View File

@ -87,7 +87,7 @@ impl Display for NameKind {
impl std::fmt::Display for RepeatedTopLevelNameErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut snd = self.kind_snd.to_string();
snd[0 .. 1].make_ascii_uppercase();
snd[0..1].make_ascii_uppercase();
write!(f, "{} '{}' has the same name as a previously defined {}", snd, self.name, self.kind_fst)
}
}

View File

@ -1,7 +1,6 @@
use crate::{
diagnostics::{Diagnostics, DiagnosticsConfig},
hvm::{self, ast::get_typ},
maybe_grow, ENTRY_POINT,
maybe_grow, multi_iterator, ENTRY_POINT,
};
// use hvmc::ast::get_typ;
use indexmap::{IndexMap, IndexSet};
@ -19,7 +18,7 @@ pub mod term_to_net;
pub mod transform;
pub use net_to_term::{net_to_term, ReadbackError};
pub use term_to_net::{book_to_nets, term_to_net};
pub use term_to_net::{book_to_hvm, term_to_hvm};
pub static STRINGS: GlobalPool<String> = GlobalPool::new();
#[derive(Debug)]
@ -247,45 +246,6 @@ pub struct Name(GlobalString);
/* Implementations */
/// A macro for creating iterators that can have statically known
/// different types. Useful for iterating over tree children, where
/// each tree node variant yields a different iterator type.
#[macro_export]
macro_rules! multi_iterator {
($Iter:ident { $($Variant:ident),* $(,)? }) => {
#[derive(Debug, Clone)]
enum $Iter<$($Variant),*> {
$($Variant { iter: $Variant }),*
}
impl<$($Variant),*> $Iter<$($Variant),*> {
$(
#[allow(non_snake_case)]
fn $Variant(iter: impl IntoIterator<IntoIter = $Variant>) -> Self {
$Iter::$Variant { iter: iter.into_iter() }
}
)*
}
impl<T, $($Variant: Iterator<Item = T>),*> Iterator for $Iter<$($Variant),*> {
type Item = T;
fn next(&mut self) -> Option<T> {
match self { $($Iter::$Variant { iter } => iter.next()),* }
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self { $($Iter::$Variant { iter } => iter.size_hint()),* }
}
}
impl<T, $($Variant: DoubleEndedIterator<Item = T>),*> DoubleEndedIterator for $Iter<$($Variant),*> {
fn next_back(&mut self) -> Option<T> {
match self { $($Iter::$Variant { iter } => iter.next_back()),* }
}
}
};
}
impl PartialEq<str> for Name {
fn eq(&self, other: &str) -> bool {
&**self == other
@ -294,13 +254,17 @@ impl PartialEq<str> for Name {
impl PartialEq<&str> for Name {
fn eq(&self, other: &&str) -> bool {
self == other
self == *other
}
}
impl PartialEq<Option<Name>> for Name {
fn eq(&self, other: &Option<Name>) -> bool {
if let Some(other) = other.as_ref() { self == other } else { false }
if let Some(other) = other.as_ref() {
self == other
} else {
false
}
}
}
@ -312,7 +276,11 @@ impl PartialEq<Name> for Option<Name> {
impl PartialEq<Option<&Name>> for Name {
fn eq(&self, other: &Option<&Name>) -> bool {
if let Some(other) = other { &self == other } else { false }
if let Some(other) = other {
&self == other
} else {
false
}
}
}
@ -449,7 +417,11 @@ impl Term {
}
pub fn var_or_era(nam: Option<Name>) -> Self {
if let Some(nam) = nam { Term::Var { nam } } else { Term::Era }
if let Some(nam) = nam {
Term::Var { nam }
} else {
Term::Era
}
}
pub fn app(fun: Term, arg: Term) -> Self {
@ -787,10 +759,10 @@ impl Term {
}
});
if let Term::Var { nam } = self
&& nam == from
{
*self = to.clone();
if let Term::Var { nam } = self {
if nam == from {
*self = to.clone();
}
}
}
@ -804,10 +776,10 @@ impl Term {
}
});
if let Term::Link { nam } = self
&& nam == from
{
*self = to.clone();
if let Term::Link { nam } = self {
if nam == from {
*self = to.clone();
}
}
}
@ -901,17 +873,17 @@ impl Num {
pub fn to_bits(&self) -> u32 {
match self {
Num::U24(val) => hvm::ast::new_u24(*val),
Num::I24(val) => hvm::ast::new_i24(*val),
Num::F24(val) => hvm::ast::new_f24(*val),
Num::U24(val) => hvm::hvm::Numb::new_u24(*val).0,
Num::I24(val) => hvm::hvm::Numb::new_i24(*val).0,
Num::F24(val) => hvm::hvm::Numb::new_f24(*val).0,
}
}
pub fn from_bits(bits: u32) -> Self {
match get_typ(bits) {
hvm::ast::TY_U24 => Num::U24(hvm::ast::get_u24(bits)),
hvm::ast::TY_I24 => Num::I24(hvm::ast::get_i24(bits)),
hvm::ast::TY_F24 => Num::F24(hvm::ast::get_f24(bits)),
match hvm::hvm::Numb::get_typ(&hvm::hvm::Numb(bits)) {
hvm::hvm::TY_U24 => Num::U24(hvm::hvm::Numb::get_u24(&hvm::hvm::Numb(bits))),
hvm::hvm::TY_I24 => Num::I24(hvm::hvm::Numb::get_i24(&hvm::hvm::Numb(bits))),
hvm::hvm::TY_F24 => Num::F24(hvm::hvm::Numb::get_f24(&hvm::hvm::Numb(bits))),
_ => unreachable!("Invalid Num bits"),
}
}
@ -1051,7 +1023,11 @@ impl Name {
}
pub fn def_name_from_generated(&self) -> Name {
if let Some((nam, _)) = self.split_once("__") { Name::new(nam) } else { self.clone() }
if let Some((nam, _)) = self.split_once("__") {
Name::new(nam)
} else {
self.clone()
}
}
}

View File

@ -1,15 +1,12 @@
use hvm::ast::{get_f24, get_i24, get_typ, get_u24};
use crate::{
diagnostics::{DiagnosticOrigin, Diagnostics, Severity},
fun::{term_to_net::Labels, Book, FanKind, Name, Op, Pattern, Tag, Term},
hvm, maybe_grow,
fun::{term_to_net::Labels, Book, FanKind, Name, Num, Op, Pattern, Tag, Term},
maybe_grow,
net::{CtrKind, INet, NodeId, NodeKind, Port, SlotId, ROOT},
};
use hvm::hvm::Numb;
use std::collections::{BTreeSet, HashMap, HashSet};
use super::Num;
/// Converts an Interaction-INet to a Lambda Calculus term
pub fn net_to_term(
net: &INet,
@ -135,27 +132,34 @@ impl Reader<'_> {
// We expect the pattern matching node to be a CON
let sel_kind = &self.net.node(sel_node).kind;
let (zero, succ) = if *sel_kind == NodeKind::Ctr(Con(None)) {
let zero_term = self.read_term(self.net.enter_port(Port(sel_node, 1)));
let mut succ_term = self.read_term(self.net.enter_port(Port(sel_node, 2)));
if sel_kind != &NodeKind::Ctr(CtrKind::Con(None)) {
// TODO: Is there any case where we expect a different node type here on readback?
self.error(ReadbackError::InvalidNumericMatch);
return Term::Err;
}
match &mut succ_term {
Term::Lam { pat: box Pattern::Var(nam), bod, .. } => {
let zero_term = self.read_term(self.net.enter_port(Port(sel_node, 1)));
let mut succ_term = self.read_term(self.net.enter_port(Port(sel_node, 2)));
// Succ term should be a lambda
let (zero, succ) = match &mut succ_term {
Term::Lam { pat, bod, .. } => {
if let Pattern::Var(nam) = pat.as_ref() {
let mut bod = std::mem::take(bod.as_mut());
if let Some(nam) = &nam {
if let Some(nam) = nam {
bod.subst(nam, &Term::Var { nam: Name::new(format!("{bnd}-1")) });
}
(zero_term, bod)
}
_ => {
} else {
// Readback should never generate non-var patterns for lambdas.
self.error(ReadbackError::InvalidNumericMatch);
(zero_term, succ_term)
}
}
} else {
// TODO: Is there any case where we expect a different node type here on readback?
self.error(ReadbackError::InvalidNumericMatch);
(Term::Err, Term::Err)
_ => {
self.error(ReadbackError::InvalidNumericMatch);
(zero_term, succ_term)
}
};
Term::Swt { arg: Box::new(arg), bnd: Some(bnd), with: vec![], pred: None, arms: vec![zero, succ] }
}
@ -175,40 +179,58 @@ impl Reader<'_> {
match next.slot() {
// If we're visiting a port 0, then it is a pair.
0 => {
if fan == FanKind::Dup
&& let Some(dup_paths) = &mut self.dup_paths
&& let stack = dup_paths.entry(lab.unwrap()).or_default()
&& let Some(slot) = stack.pop()
{
// Since we had a paired Dup in the path to this Sup,
// we "decay" the superposition according to the original direction we came from the Dup.
let term = self.read_term(self.net.enter_port(Port(node, slot)));
self.dup_paths.as_mut().unwrap().get_mut(&lab.unwrap()).unwrap().push(slot);
term
} else {
// If no Dup with same label in the path, we can't resolve the Sup, so keep it as a term.
self.decay_or_get_ports(node).map_or_else(
|(fst, snd)| Term::Fan { fan, tag: self.labels[fan].to_tag(lab), els: vec![fst, snd] },
|term| term,
)
// If this superposition is in a readback path with a paired Dup,
// we resolve it by splitting the two sup values into the two Dup variables.
// If we find that it's not paired with a Dup, we just keep the Sup as a term.
// The latter are all the early returns.
if fan != FanKind::Dup {
return self.decay_or_get_ports(node).unwrap_or_else(|(fst, snd)| Term::Fan {
fan,
tag: self.labels[fan].to_tag(lab),
els: vec![fst, snd],
});
}
let Some(dup_paths) = &mut self.dup_paths else {
return self.decay_or_get_ports(node).unwrap_or_else(|(fst, snd)| Term::Fan {
fan,
tag: self.labels[fan].to_tag(lab),
els: vec![fst, snd],
});
};
let stack = dup_paths.entry(lab.unwrap()).or_default();
let Some(slot) = stack.pop() else {
return self.decay_or_get_ports(node).unwrap_or_else(|(fst, snd)| Term::Fan {
fan,
tag: self.labels[fan].to_tag(lab),
els: vec![fst, snd],
});
};
// Found a paired Dup, so we "decay" the superposition according to the original direction we came from the Dup.
let term = self.read_term(self.net.enter_port(Port(node, slot)));
self.dup_paths.as_mut().unwrap().get_mut(&lab.unwrap()).unwrap().push(slot);
term
}
// If we're visiting a port 1 or 2, then it is a variable.
// Also, that means we found a dup, so we store it to read later.
1 | 2 => {
if fan == FanKind::Dup
&& let Some(dup_paths) = &mut self.dup_paths
{
dup_paths.entry(lab.unwrap()).or_default().push(next.slot());
let term = self.read_term(self.net.enter_port(Port(node, 0)));
self.dup_paths.as_mut().unwrap().entry(lab.unwrap()).or_default().pop().unwrap();
term
} else {
if self.seen_fans.insert(node) {
self.scope.insert(node);
// If doing non-linear readback, we also store dup paths to try to resolve them later.
if let Some(dup_paths) = &mut self.dup_paths {
if fan == FanKind::Dup {
dup_paths.entry(lab.unwrap()).or_default().push(next.slot());
let term = self.read_term(self.net.enter_port(Port(node, 0)));
self.dup_paths.as_mut().unwrap().entry(lab.unwrap()).or_default().pop().unwrap();
return term;
}
Term::Var { nam: self.namegen.var_name(next) }
}
// Otherwise, just store the new dup/let tup and return the variable.
if self.seen_fans.insert(node) {
self.scope.insert(node);
}
Term::Var { nam: self.namegen.var_name(next) }
}
_ => unreachable!(),
}
@ -224,11 +246,12 @@ impl Reader<'_> {
let opr_node = self.net.enter_port(Port(port0_node, 0)).node();
let opr_kind = self.net.node(opr_node).kind.clone();
let opr = if let NodeKind::Num { val } = opr_kind {
if get_typ(val) != hvm::ast::TY_SYM {
let typ = hvm::hvm::Numb::get_typ(&Numb(val));
if typ != hvm::hvm::TY_SYM {
self.error(ReadbackError::InvalidNumericOp);
return Term::Err;
}
if let Some(op) = Op::from_native_tag(val, NumType::U24) {
if let Some(op) = Op::from_native_tag(typ, NumType::U24) {
op
} else {
self.error(ReadbackError::InvalidNumericOp);
@ -378,32 +401,32 @@ enum NumType {
}
impl Op {
fn from_native_tag(val: u32, typ: NumType) -> Option<Op> {
fn from_native_tag(val: hvm::hvm::Tag, typ: NumType) -> Option<Op> {
let op = match val {
0x4 => Op::ADD,
0x5 => Op::SUB,
0x6 => Op::MUL,
0x7 => Op::DIV,
0x8 => Op::REM,
0x9 => Op::EQ,
0xa => Op::NEQ,
0xb => Op::LT,
0xc => Op::GT,
0xd => {
hvm::hvm::OP_ADD => Op::ADD,
hvm::hvm::OP_SUB => Op::SUB,
hvm::hvm::OP_MUL => Op::MUL,
hvm::hvm::OP_DIV => Op::DIV,
hvm::hvm::OP_REM => Op::REM,
hvm::hvm::OP_EQ => Op::EQ,
hvm::hvm::OP_NEQ => Op::NEQ,
hvm::hvm::OP_LT => Op::LT,
hvm::hvm::OP_GT => Op::GT,
hvm::hvm::OP_AND => {
if typ == NumType::F24 {
Op::ATN
} else {
Op::AND
}
}
0xe => {
hvm::hvm::OP_OR => {
if typ == NumType::F24 {
Op::LOG
} else {
Op::OR
}
}
0xf => {
hvm::hvm::OP_XOR => {
if typ == NumType::F24 {
Op::POW
} else {
@ -477,12 +500,12 @@ impl Term {
}
fn num_from_bits_with_type(val: u32, typ: u32) -> Term {
match get_typ(typ) {
match hvm::hvm::Numb::get_typ(&Numb(typ)) {
// No type information, assume u24 by default
hvm::ast::TY_SYM => Term::Num { val: Num::U24(get_u24(val)) },
hvm::ast::TY_U24 => Term::Num { val: Num::U24(get_u24(val)) },
hvm::ast::TY_I24 => Term::Num { val: Num::I24(get_i24(val)) },
hvm::ast::TY_F24 => Term::Num { val: Num::F24(get_f24(val)) },
hvm::hvm::TY_SYM => Term::Num { val: Num::U24(Numb::get_u24(&Numb(val))) },
hvm::hvm::TY_U24 => Term::Num { val: Num::U24(Numb::get_u24(&Numb(val))) },
hvm::hvm::TY_I24 => Term::Num { val: Num::I24(Numb::get_i24(&Numb(val))) },
hvm::hvm::TY_F24 => Term::Num { val: Num::F24(Numb::get_f24(&Numb(val))) },
_ => Term::Err,
}
}
@ -591,12 +614,13 @@ impl Term {
})
}
/// Transform the variables that we previously found were unscoped into their unscoped variants.
pub fn apply_unscoped(&mut self, unscoped: &HashSet<Name>) {
maybe_grow(|| {
if let Term::Var { nam } = self
&& unscoped.contains(nam)
{
*self = Term::Link { nam: std::mem::take(nam) }
if let Term::Var { nam } = self {
if unscoped.contains(nam) {
*self = Term::Link { nam: std::mem::take(nam) }
}
}
if let Some(pat) = self.pattern_mut() {
pat.apply_unscoped(unscoped);
@ -611,11 +635,11 @@ impl Term {
impl Pattern {
fn apply_unscoped(&mut self, unscoped: &HashSet<Name>) {
maybe_grow(|| {
if let Pattern::Var(Some(nam)) = self
&& unscoped.contains(nam)
{
let nam = std::mem::take(nam);
*self = Pattern::Chn(nam);
if let Pattern::Var(Some(nam)) = self {
if unscoped.contains(nam) {
let nam = std::mem::take(nam);
*self = Pattern::Chn(nam);
}
}
for child in self.children_mut() {
child.apply_unscoped(unscoped)

View File

@ -706,7 +706,7 @@ impl<'a> Parser<'a> for TermParser<'a> {
/// Override to have our own error message.
fn consume(&mut self, text: &str) -> ParseResult<()> {
self.skip_trivia();
if self.input().get(*self.index() ..).unwrap_or_default().starts_with(text) {
if self.input().get(*self.index()..).unwrap_or_default().starts_with(text) {
*self.index() += text.len();
Ok(())
} else {
@ -834,7 +834,7 @@ pub trait ParserCommons<'a>: Parser<'a> {
/// Consumes exactly the text without skipping.
fn consume_exactly(&mut self, text: &str) -> ParseResult<()> {
if self.input().get(*self.index() ..).unwrap_or_default().starts_with(text) {
if self.input().get(*self.index()..).unwrap_or_default().starts_with(text) {
*self.index() += text.len();
Ok(())
} else {
@ -940,7 +940,7 @@ pub trait ParserCommons<'a>: Parser<'a> {
if !self.starts_with(keyword) {
return false;
}
let input = &self.input()[*self.index() + keyword.len() ..];
let input = &self.input()[*self.index() + keyword.len()..];
let next_is_name = input.chars().next().map_or(false, is_name_char);
if !next_is_name {
self.consume_exactly(keyword).unwrap();
@ -954,7 +954,7 @@ pub trait ParserCommons<'a>: Parser<'a> {
let ini_idx = *self.index();
self.consume_exactly(keyword)?;
let end_idx = *self.index();
let input = &self.input()[*self.index() ..];
let input = &self.input()[*self.index()..];
let next_is_name = input.chars().next().map_or(false, is_name_char);
if !next_is_name {
Ok(())
@ -985,7 +985,7 @@ pub trait ParserCommons<'a>: Parser<'a> {
let mut els = vec![];
// Consume the minimum number of elements
for i in 0 .. min_els {
for i in 0..min_els {
self.skip_trivia();
els.push(parser(self)?);
self.skip_trivia();
@ -1097,8 +1097,16 @@ pub trait ParserCommons<'a>: Parser<'a> {
};
let num_str = self.take_while(move |c| c.is_digit(radix) || c == '_');
let num_str = num_str.chars().filter(|c| *c != '_').collect::<String>();
if num_str.is_empty() {
self.expected("numeric digit")
let next_is_hex = self.peek_one().map_or(false, |c| "0123456789abcdefABCDEF".contains(c));
if next_is_hex || num_str.is_empty() {
let base = match radix {
16 => "hexadecimal",
10 => "decimal",
2 => "binary",
_ => unreachable!(),
};
self.expected(format!("valid {base} digit").as_str())
} else {
u32::from_str_radix(&num_str, radix).map_err(|e| e.to_string())
}
@ -1142,7 +1150,7 @@ pub trait ParserCommons<'a>: Parser<'a> {
// I24
if let Some(sgn) = sgn {
let num = sgn * num as i32;
if !(-0x00800000 ..= 0x007fffff).contains(&num) {
if !(-0x00800000..=0x007fffff).contains(&num) {
return self.num_range_err(ini_idx, "I24");
}
return Ok(Num::I24(num));
@ -1175,9 +1183,9 @@ pub trait ParserCommons<'a>: Parser<'a> {
let Some(c) = self.advance_one() else { self.expected("base_64 character")? };
let c = c as u8;
let nxt = match c {
b'A' ..= b'Z' => c - b'A',
b'a' ..= b'z' => c - b'a' + 26,
b'0' ..= b'9' => c - b'0' + 52,
b'A'..=b'Z' => c - b'A',
b'a'..=b'z' => c - b'a' + 26,
b'0'..=b'9' => c - b'0' + 52,
b'+' => 62,
b'/' => 63,
_ => return self.expected("base64 character"),

View File

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

View File

@ -137,12 +137,12 @@ impl Term {
}
// If we found a recursive field, replace with a call to the new function.
if let Term::Var { nam } = self
&& recursive.contains(nam)
{
let call = Term::call(Term::Ref { nam: def_name.clone() }, [std::mem::take(self)]);
let call = Term::call(call, free_vars.iter().cloned().map(|nam| Term::Var { nam }));
*self = call;
if let Term::Var { nam } = self {
if recursive.contains(nam) {
let call = Term::call(Term::Ref { nam: def_name.clone() }, [std::mem::take(self)]);
let call = Term::call(call, free_vars.iter().cloned().map(|nam| Term::Var { nam }));
*self = call;
}
}
})
}

View File

@ -41,7 +41,7 @@ impl Definition {
let repeated_bind_errs = fix_repeated_binds(&mut self.rules);
errs.extend(repeated_bind_errs);
let args = (0 .. self.arity()).map(|i| Name::new(format!("%arg{i}"))).collect::<Vec<_>>();
let args = (0..self.arity()).map(|i| Name::new(format!("%arg{i}"))).collect::<Vec<_>>();
let rules = std::mem::take(&mut self.rules);
match simplify_rule_match(args.clone(), rules, vec![], ctrs, adts) {
Ok(body) => {
@ -198,7 +198,7 @@ fn fan_rule(
) -> Result<Term, DesugarMatchDefErr> {
let arg = args[0].clone();
let old_args = args.split_off(1);
let new_args = (0 .. len).map(|i| Name::new(format!("{arg}.{i}")));
let new_args = (0..len).map(|i| Name::new(format!("{arg}.{i}")));
let mut new_rules = vec![];
for mut rule in rules {
@ -272,7 +272,7 @@ fn num_rule(
match &rule.pats[0] {
Pattern::Num(n) if n == num => {
let body = rule.body.clone();
let rule = Rule { pats: rule.pats[1 ..].to_vec(), body };
let rule = Rule { pats: rule.pats[1..].to_vec(), body };
new_rules.push(rule);
}
Pattern::Var(var) => {
@ -284,7 +284,7 @@ fn num_rule(
nxt: Box::new(std::mem::take(&mut body)),
};
}
let rule = Rule { pats: rule.pats[1 ..].to_vec(), body };
let rule = Rule { pats: rule.pats[1..].to_vec(), body };
new_rules.push(rule);
}
_ => (),
@ -304,7 +304,7 @@ fn num_rule(
let var_recovered = Term::add_num(Term::Var { nam: pred_var.clone() }, Num::U24(1 + last_num));
body = Term::Use { nam: Some(var.clone()), val: Box::new(var_recovered), nxt: Box::new(body) };
}
let rule = Rule { pats: rule.pats[1 ..].to_vec(), body };
let rule = Rule { pats: rule.pats[1..].to_vec(), body };
new_rules.push(rule);
}
}
@ -405,7 +405,7 @@ fn switch_rule(
let mut new_rules = vec![];
for rule in &rules {
let old_pats = rule.pats[1 ..].to_vec();
let old_pats = rule.pats[1..].to_vec();
match &rule.pats[0] {
// Same ctr, extract subpatterns.
// (Ctr pat0_0 ... pat0_m) pat1 ... patN: body

View File

@ -55,13 +55,16 @@ fn encode_match(arg: Term, rules: Vec<MatchRule>, adt_encoding: AdtEncoding) ->
maybe_grow(|| match arms {
[] => Term::Err,
[arm] => Term::lam(Pattern::Var(None), std::mem::take(arm)),
[arm, rest @ ..] => Term::lam(Pattern::Var(Some(Name::new("%tag"))), Term::Swt {
arg: Box::new(Term::Var { nam: Name::new("%tag") }),
bnd: None,
with: vec![],
pred: None,
arms: vec![std::mem::take(arm), make_switches(rest)],
}),
[arm, rest @ ..] => Term::lam(
Pattern::Var(Some(Name::new("%tag"))),
Term::Swt {
arg: Box::new(Term::Var { nam: Name::new("%tag") }),
bnd: None,
with: vec![],
pred: None,
arms: vec![std::mem::take(arm), make_switches(rest)],
},
),
})
}
let mut arms =

View File

@ -131,40 +131,42 @@ impl Term {
let bnd = bnd.clone().unwrap();
// Normalize arms, making one arm for each constructor of the matched adt.
if let Some(ctr_nam) = &arms[0].0
&& let Some(adt_nam) = ctrs.get(ctr_nam)
{
let adt_ctrs = &adts[adt_nam].ctrs;
if let Some(ctr_nam) = &arms[0].0 {
if let Some(adt_nam) = ctrs.get(ctr_nam) {
// First arm matches a constructor as expected, so we can normalize the arms.
let adt_ctrs = &adts[adt_nam].ctrs;
// Decide which constructor corresponds to which arm of the match.
let mut bodies = fixed_match_arms(&bnd, arms, adt_nam, adt_ctrs.keys(), ctrs, adts, errs);
// Decide which constructor corresponds to which arm of the match.
let mut bodies = fixed_match_arms(&bnd, arms, adt_nam, adt_ctrs.keys(), ctrs, adts, errs);
// Build the match arms, with all constructors
let mut new_rules = vec![];
for (ctr, fields) in adt_ctrs.iter() {
let fields = fields.iter().map(|f| Some(match_field(&bnd, &f.nam))).collect::<Vec<_>>();
let body = if let Some(Some(body)) = bodies.remove(ctr) {
body
} else {
errs.push(FixMatchErr::NonExhaustiveMatch { typ: adt_nam.clone(), missing: ctr.clone() });
Term::Err
};
new_rules.push((Some(ctr.clone()), fields, body));
}
*arms = new_rules;
} else {
// First arm was not matching a constructor, convert into a use term.
errs.push(FixMatchErr::IrrefutableMatch { var: arms[0].0.clone() });
let match_var = arms[0].0.take();
*self = std::mem::take(&mut arms[0].2);
if let Some(var) = match_var {
*self = Term::Use {
nam: Some(var),
val: Box::new(Term::Var { nam: bnd }),
nxt: Box::new(std::mem::take(self)),
};
// Build the match arms, with all constructors
let mut new_rules = vec![];
for (ctr, fields) in adt_ctrs.iter() {
let fields = fields.iter().map(|f| Some(match_field(&bnd, &f.nam))).collect::<Vec<_>>();
let body = if let Some(Some(body)) = bodies.remove(ctr) {
body
} else {
errs.push(FixMatchErr::NonExhaustiveMatch { typ: adt_nam.clone(), missing: ctr.clone() });
Term::Err
};
new_rules.push((Some(ctr.clone()), fields, body));
}
*arms = new_rules;
return;
}
}
// First arm was not matching a constructor, irrefutable match, convert into a use term.
errs.push(FixMatchErr::IrrefutableMatch { var: arms[0].0.clone() });
let match_var = arms[0].0.take();
*self = std::mem::take(&mut arms[0].2);
if let Some(var) = match_var {
*self = Term::Use {
nam: Some(var),
val: Box::new(Term::Var { nam: bnd }),
nxt: Box::new(std::mem::take(self)),
};
}
}
}
@ -183,49 +185,49 @@ fn fixed_match_arms<'a>(
errs: &mut Vec<FixMatchErr>,
) -> HashMap<&'a Name, Option<Term>> {
let mut bodies = HashMap::<&Name, Option<Term>>::from_iter(adt_ctrs.map(|ctr| (ctr, None)));
for rule_idx in 0 .. rules.len() {
if let Some(ctr_nam) = &rules[rule_idx].0
&& let Some(found_adt) = ctrs.get(ctr_nam)
{
// Ctr arm, use the body of this rule for this constructor.
if found_adt == adt_nam {
let body = bodies.get_mut(ctr_nam).unwrap();
if body.is_none() {
// Use this rule for this constructor
*body = Some(rules[rule_idx].2.clone());
} else {
errs.push(FixMatchErr::RedundantArm { ctr: ctr_nam.clone() });
}
} else {
errs.push(FixMatchErr::AdtMismatch {
expected: adt_nam.clone(),
found: found_adt.clone(),
ctr: ctr_nam.clone(),
})
}
} else {
// Var arm, use the body of this rule for all non-covered constructors.
for (ctr, body) in bodies.iter_mut() {
if body.is_none() {
let mut new_body = rules[rule_idx].2.clone();
if let Some(var) = &rules[rule_idx].0 {
new_body = Term::Use {
nam: Some(var.clone()),
val: Box::new(rebuild_ctr(bnd, ctr, &adts[adt_nam].ctrs[&**ctr])),
nxt: Box::new(new_body),
};
for rule_idx in 0..rules.len() {
// If Ctr arm, use the body of this rule for this constructor.
if let Some(ctr_nam) = &rules[rule_idx].0 {
if let Some(found_adt) = ctrs.get(ctr_nam) {
if found_adt == adt_nam {
let body = bodies.get_mut(ctr_nam).unwrap();
if body.is_none() {
// Use this rule for this constructor
*body = Some(rules[rule_idx].2.clone());
} else {
errs.push(FixMatchErr::RedundantArm { ctr: ctr_nam.clone() });
}
*body = Some(new_body);
} else {
errs.push(FixMatchErr::AdtMismatch {
expected: adt_nam.clone(),
found: found_adt.clone(),
ctr: ctr_nam.clone(),
})
}
continue;
}
if rule_idx != rules.len() - 1 {
errs.push(FixMatchErr::UnreachableMatchArms { var: rules[rule_idx].0.clone() });
rules.truncate(rule_idx + 1);
}
break;
}
// Otherwise, Var arm, use the body of this rule for all non-covered constructors.
for (ctr, body) in bodies.iter_mut() {
if body.is_none() {
let mut new_body = rules[rule_idx].2.clone();
if let Some(var) = &rules[rule_idx].0 {
new_body = Term::Use {
nam: Some(var.clone()),
val: Box::new(rebuild_ctr(bnd, ctr, &adts[adt_nam].ctrs[&**ctr])),
nxt: Box::new(new_body),
};
}
*body = Some(new_body);
}
}
if rule_idx != rules.len() - 1 {
errs.push(FixMatchErr::UnreachableMatchArms { var: rules[rule_idx].0.clone() });
rules.truncate(rule_idx + 1);
}
break;
}
bodies
}

View File

@ -31,52 +31,53 @@ impl Term {
}
fn term_to_linear(term: &mut Term, var_uses: &mut HashMap<Name, u64>) {
maybe_grow(|| match term {
Term::Let { pat: box Pattern::Var(Some(nam)), val, nxt } => {
// TODO: This is swapping the order of how the bindings are
// used, since it's not following the usual AST order (first
// val, then nxt). Doesn't change behaviour, but looks strange.
term_to_linear(nxt, var_uses);
maybe_grow(|| {
if let Term::Let { pat, val, nxt } = term {
if let Pattern::Var(Some(nam)) = pat.as_ref() {
// TODO: This is swapping the order of how the bindings are
// used, since it's not following the usual AST order (first
// val, then nxt). Doesn't change behaviour, but looks strange.
term_to_linear(nxt, var_uses);
let uses = get_var_uses(Some(nam), var_uses);
term_to_linear(val, var_uses);
match uses {
0 => {
let Term::Let { pat, .. } = term else { unreachable!() };
**pat = Pattern::Var(None);
}
1 => {
nxt.subst(nam, val.as_ref());
*term = std::mem::take(nxt.as_mut());
}
_ => {
let new_pat = duplicate_pat(nam, uses);
let Term::Let { pat, .. } = term else { unreachable!() };
*pat = new_pat;
let uses = get_var_uses(Some(nam), var_uses);
term_to_linear(val, var_uses);
match uses {
0 => {
let Term::Let { pat, .. } = term else { unreachable!() };
**pat = Pattern::Var(None);
}
1 => {
nxt.subst(nam, val.as_ref());
*term = std::mem::take(nxt.as_mut());
}
_ => {
let new_pat = duplicate_pat(nam, uses);
let Term::Let { pat, .. } = term else { unreachable!() };
*pat = new_pat;
}
}
return;
}
}
Term::Var { nam } => {
if let Term::Var { nam } = term {
let instantiated_count = var_uses.entry(nam.clone()).or_default();
*instantiated_count += 1;
*nam = dup_name(nam, *instantiated_count);
return;
}
_ => {
for (child, binds) in term.children_mut_with_binds_mut() {
term_to_linear(child, var_uses);
for (child, binds) in term.children_mut_with_binds_mut() {
term_to_linear(child, var_uses);
for bind in binds {
let uses = get_var_uses(bind.as_ref(), var_uses);
match uses {
// Erase binding
0 => *bind = None,
// Keep as-is
1 => (),
// Duplicate binding
uses => duplicate_term(bind.as_ref().unwrap(), child, uses, None),
}
for bind in binds {
let uses = get_var_uses(bind.as_ref(), var_uses);
match uses {
// Erase binding
0 => *bind = None,
// Keep as-is
1 => (),
// Duplicate binding
uses => duplicate_term(bind.as_ref().unwrap(), child, uses, None),
}
}
}
@ -108,10 +109,14 @@ fn duplicate_pat(nam: &Name, uses: u64) -> Box<Pattern> {
Box::new(Pattern::Fan(
FanKind::Dup,
Tag::Auto,
(1 .. uses + 1).map(|i| Pattern::Var(Some(dup_name(nam, i)))).collect(),
(1..uses + 1).map(|i| Pattern::Var(Some(dup_name(nam, i)))).collect(),
))
}
fn dup_name(nam: &Name, uses: u64) -> Name {
if uses == 1 { nam.clone() } else { Name::new(format!("{nam}_{uses}")) }
if uses == 1 {
nam.clone()
} else {
Name::new(format!("{nam}_{uses}"))
}
}

View File

@ -46,19 +46,19 @@ impl Term {
scope: &mut HashMap<&'a Name, usize>,
) -> Result<(), String> {
maybe_grow(move || {
if let Term::Var { nam } = self
&& is_var_in_scope(nam, scope)
{
// If the variable is actually a reference to main, don't swap and return an error.
if let Some(main) = main
&& nam == main
{
return Err("Main definition can't be referenced inside the program.".to_string());
}
if let Term::Var { nam } = self {
if is_var_in_scope(nam, scope) {
// If the variable is actually a reference to main, don't swap and return an error.
if let Some(main) = main {
if nam == main {
return Err("Main definition can't be referenced inside the program.".to_string());
}
}
// If the variable is actually a reference to a function, swap the term.
if def_names.contains(nam) {
*self = Term::r#ref(nam);
// If the variable is actually a reference to a function, swap the term.
if def_names.contains(nam) {
*self = Term::r#ref(nam);
}
}
}

View File

@ -19,50 +19,56 @@ impl Term {
// Search for a List/Cons pattern in the term and try to build a list from that point on.
// If successful, replace the term with the list.
// If not, keep as-is.
match self {
// Nil: List/nil
Term::Ref { nam } if nam == builtins::LNIL => *self = Term::List { els: vec![] },
// Cons: @x (x CONS_TAG <term> <term>)
Term::Lam {
tag: Tag::Static,
pat: box Pattern::Var(Some(var_lam)),
bod:
box Term::App {
tag: Tag::Static,
fun:
box Term::App {
tag: Tag::Static,
fun:
box Term::App {
tag: Tag::Static,
fun: box Term::Var { nam: var_app },
arg: box Term::Num { val: Num::U24(LCONS_TAG) },
},
arg: head,
},
arg: tail,
},
} if var_lam == var_app => {
head.resugar_lists_num_scott();
if let Some(els) = build_list_num_scott(tail, vec![head]) {
*self = Term::List { els };
} else {
// Not a list term, keep as-is.
// Nil: List/nil
if let Term::Ref { nam } = self {
if nam == builtins::LNIL {
*self = Term::List { els: vec![] };
}
}
// Cons: @x (x CONS_TAG <term> <term>)
if let Term::Lam { tag: Tag::Static, pat, bod } = self {
if let Pattern::Var(Some(var_lam)) = pat.as_mut() {
if let Term::App { tag: Tag::Static, fun, arg: tail } = bod.as_mut() {
if let Term::App { tag: Tag::Static, fun, arg: head } = fun.as_mut() {
if let Term::App { tag: Tag::Static, fun, arg } = fun.as_mut() {
if let Term::Var { nam: var_app } = fun.as_mut() {
if let Term::Num { val: Num::U24(LCONS_TAG) } = arg.as_mut() {
if var_lam == var_app {
let l = build_list_num_scott(tail.as_mut(), vec![std::mem::take(head)]);
match l {
Ok(l) => *self = Term::List { els: l.into_iter().map(|x| *x).collect() },
// Was not a list term, keep as-is.
Err(mut l) => {
*head = l.pop().unwrap();
assert!(l.is_empty())
}
}
}
}
}
}
}
}
}
// Cons: (List/Cons <term> <term>)
Term::App {
tag: Tag::Static,
fun: box Term::App { tag: Tag::Static, fun: box Term::Ref { nam }, arg: head },
arg: tail,
} if nam == builtins::LCONS => {
if let Some(els) = build_list_num_scott(tail, vec![head]) {
*self = Term::List { els };
} else {
// Not a list term, keep as-is.
}
// Cons: (List/Cons <term> <term>)
if let Term::App { tag: Tag::Static, fun, arg: tail } = self {
if let Term::App { tag: Tag::Static, fun, arg: head } = fun.as_mut() {
if let Term::Ref { nam } = fun.as_mut() {
if nam == builtins::LCONS {
let l = build_list_num_scott(tail.as_mut(), vec![std::mem::take(head)]);
match l {
Ok(l) => *self = Term::List { els: l.into_iter().map(|x| *x).collect() },
// Was not a list term, keep as-is.
Err(mut l) => {
*head = l.pop().unwrap();
assert!(l.is_empty())
}
}
}
}
}
_ => (),
}
for child in self.children_mut() {
@ -77,45 +83,56 @@ impl Term {
// Search for a List/Cons pattern in the term and try to build a list from that point on.
// If successful, replace the term with the list.
// If not, keep as-is.
match self {
// Nil: List/nil
Term::Ref { nam } if nam == builtins::LNIL => *self = Term::List { els: vec![] },
// Cons: @* @c (c <term> <term>)
Term::Lam {
tag: Tag::Static,
pat: box Pattern::Var(None),
bod:
box Term::Lam {
tag: Tag::Static,
pat: box Pattern::Var(Some(nam_cons_lam)),
bod:
box Term::App {
tag: Tag::Static,
fun: box Term::App { tag: Tag::Static, fun: box Term::Var { nam: nam_cons_app }, arg: head },
arg: tail,
},
},
} if nam_cons_lam == nam_cons_app => {
head.resugar_lists_scott();
if let Some(els) = build_list_scott(tail, vec![head]) {
*self = Term::List { els };
} else {
// Not a list term, keep as-is.
// Nil: List/nil
if let Term::Ref { nam } = self {
if nam == builtins::LNIL {
*self = Term::List { els: vec![] };
}
}
// Cons: @* @c (c <term> <term>)
if let Term::Lam { tag: Tag::Static, pat, bod } = self {
if let Pattern::Var(None) = pat.as_mut() {
if let Term::Lam { tag: Tag::Static, pat, bod } = bod.as_mut() {
if let Pattern::Var(Some(var_lam)) = pat.as_mut() {
if let Term::App { tag: Tag::Static, fun, arg: tail } = bod.as_mut() {
if let Term::App { tag: Tag::Static, fun, arg: head } = fun.as_mut() {
if let Term::Var { nam: var_app } = fun.as_mut() {
if var_lam == var_app {
let l = build_list_scott(tail.as_mut(), vec![std::mem::take(head)]);
match l {
Ok(l) => *self = Term::List { els: l.into_iter().map(|x| *x).collect() },
// Was not a list term, keep as-is.
Err(mut l) => {
*head = l.pop().unwrap();
assert!(l.is_empty())
}
}
}
}
}
}
}
}
}
// Cons: (List/Cons <term> <term>)
Term::App {
tag: Tag::Static,
fun: box Term::App { tag: Tag::Static, fun: box Term::Ref { nam }, arg: head },
arg: tail,
} if nam == builtins::LCONS => {
if let Some(els) = build_list_scott(tail, vec![head]) {
*self = Term::List { els };
} else {
// Not a list term, keep as-is.
}
// Cons: (List/Cons <term> <term>)
if let Term::App { tag: Tag::Static, fun, arg: tail } = self {
if let Term::App { tag: Tag::Static, fun, arg: head } = fun.as_mut() {
if let Term::Ref { nam } = fun.as_mut() {
if nam == builtins::LCONS {
let l = build_list_scott(tail.as_mut(), vec![std::mem::take(head)]);
match l {
Ok(l) => *self = Term::List { els: l.into_iter().map(|x| *x).collect() },
// Was not a list term, keep as-is.
Err(mut l) => {
*head = l.pop().unwrap();
assert!(l.is_empty())
}
}
}
}
}
_ => (),
}
for child in self.children_mut() {
@ -125,93 +142,128 @@ impl Term {
}
}
fn build_list_num_scott(term: &mut Term, mut s: Vec<&mut Term>) -> Option<Vec<Term>> {
// TODO: We have to do weird manipulations with Box<Term> because of the borrow checker.
// When we used use box patterns this was a way simpler match statement.
#[allow(clippy::vec_box)]
fn build_list_num_scott(term: &mut Term, mut l: Vec<Box<Term>>) -> Result<Vec<Box<Term>>, Vec<Box<Term>>> {
maybe_grow(|| {
match term {
// Nil: List/Nil
Term::Ref { nam } if nam == builtins::LNIL => Some(s.into_iter().map(std::mem::take).collect()),
// Cons: @x (x CONS_TAG <term> <term>)
Term::Lam {
tag: Tag::Static,
pat: box Pattern::Var(Some(var_lam)),
bod:
box Term::App {
tag: Tag::Static,
fun:
box Term::App {
tag: Tag::Static,
fun:
box Term::App {
tag: Tag::Static,
fun: box Term::Var { nam: var_app },
arg: box Term::Num { val: Num::U24(LCONS_TAG) },
},
arg: head,
},
arg: tail,
},
} if var_lam == var_app => {
// New list element, append and recurse
s.push(head.as_mut());
build_list_num_scott(tail, s)
}
// Cons: (List/cons <term> <term>)
Term::App {
tag: Tag::Static,
fun: box Term::App { tag: Tag::Static, fun: box Term::Ref { nam }, arg: head },
arg: tail,
} if nam == builtins::LCONS => {
// New list element, append and recurse
s.push(head.as_mut());
build_list_num_scott(tail, s)
}
_ => {
// Not a list term, stop
None
// Nil: List/nil
if let Term::Ref { nam } = term {
if nam == builtins::LNIL {
return Ok(l);
}
}
// Cons: @x (x CONS_TAG <term> <term>)
if let Term::Lam { tag: Tag::Static, pat, bod } = term {
if let Pattern::Var(Some(var_lam)) = pat.as_mut() {
if let Term::App { tag: Tag::Static, fun, arg: tail } = bod.as_mut() {
if let Term::App { tag: Tag::Static, fun, arg: head } = fun.as_mut() {
if let Term::App { tag: Tag::Static, fun, arg } = fun.as_mut() {
if let Term::Var { nam: var_app } = fun.as_mut() {
if let Term::Num { val: Num::U24(LCONS_TAG) } = arg.as_mut() {
if var_lam == var_app {
// New list element, append and recurse
l.push(std::mem::take(head));
let l = build_list_num_scott(tail, l);
match l {
Ok(l) => return Ok(l),
Err(mut l) => {
// If it wasn't a list, we have to put it back.
*head = l.pop().unwrap();
return Err(l);
}
}
}
}
}
}
}
}
}
}
// Cons: (List/Cons <term> <term>)
if let Term::App { tag: Tag::Static, fun, arg: tail } = term {
if let Term::App { tag: Tag::Static, fun, arg: head } = fun.as_mut() {
if let Term::Ref { nam } = fun.as_mut() {
if nam == builtins::LCONS {
// New list element, append and recurse
l.push(std::mem::take(head));
let l = build_list_num_scott(tail, l);
match l {
Ok(l) => return Ok(l),
Err(mut l) => {
// If it wasn't a list, we have to put it back.
*head = l.pop().unwrap();
return Err(l);
}
}
}
}
}
}
// Not a list term, stop
Err(l)
})
}
fn build_list_scott(term: &mut Term, mut s: Vec<&mut Term>) -> Option<Vec<Term>> {
#[allow(clippy::vec_box)]
fn build_list_scott(term: &mut Term, mut l: Vec<Box<Term>>) -> Result<Vec<Box<Term>>, Vec<Box<Term>>> {
maybe_grow(|| {
match term {
// Nil: List/Nil
Term::Ref { nam } if nam == builtins::LNIL => Some(s.into_iter().map(std::mem::take).collect()),
// Cons: @* @c (c <term> <term>)
Term::Lam {
tag: Tag::Static,
pat: box Pattern::Var(None),
bod:
box Term::Lam {
tag: Tag::Static,
pat: box Pattern::Var(Some(nam_cons_lam)),
bod:
box Term::App {
tag: Tag::Static,
fun: box Term::App { tag: Tag::Static, fun: box Term::Var { nam: nam_cons_app }, arg: head },
arg: tail,
},
},
} if nam_cons_lam == nam_cons_app => {
// New list element, append and recurse
s.push(head.as_mut());
build_list_scott(tail, s)
}
// Cons: (List/cons <term> <term>)
Term::App {
tag: Tag::Static,
fun: box Term::App { tag: Tag::Static, fun: box Term::Ref { nam }, arg: head },
arg: tail,
} if nam == builtins::LCONS => {
// New list element, append and recurse
s.push(head.as_mut());
build_list_scott(tail, s)
}
_ => {
// Not a list term, stop
None
// Nil: List/nil
if let Term::Ref { nam } = term {
if nam == builtins::LNIL {
return Ok(l);
}
}
// Cons: @* @c (c <term> <term>)
if let Term::Lam { tag: Tag::Static, pat, bod } = term {
if let Pattern::Var(None) = pat.as_mut() {
if let Term::Lam { tag: Tag::Static, pat, bod } = bod.as_mut() {
if let Pattern::Var(Some(var_lam)) = pat.as_mut() {
if let Term::App { tag: Tag::Static, fun, arg: tail } = bod.as_mut() {
if let Term::App { tag: Tag::Static, fun, arg: head } = fun.as_mut() {
if let Term::Var { nam: var_app } = fun.as_mut() {
if var_lam == var_app {
// New list element, append and recurse
l.push(std::mem::take(head));
let l = build_list_scott(tail, l);
match l {
Ok(l) => return Ok(l),
Err(mut l) => {
// If it wasn't a list, we have to put it back.
*head = l.pop().unwrap();
return Err(l);
}
}
}
}
}
}
}
}
}
}
// Cons: (List/Cons <term> <term>)
if let Term::App { tag: Tag::Static, fun, arg: tail } = term {
if let Term::App { tag: Tag::Static, fun, arg: head } = fun.as_mut() {
if let Term::Ref { nam } = fun.as_mut() {
if nam == builtins::LCONS {
// New list element, append and recurse
l.push(std::mem::take(head));
let l = build_list_scott(tail, l);
match l {
Ok(l) => return Ok(l),
Err(mut l) => {
// If it wasn't a list, we have to put it back.
*head = l.pop().unwrap();
return Err(l);
}
}
}
}
}
}
// Not a list term, stop
Err(l)
})
}

View File

@ -20,56 +20,54 @@ impl Term {
// Search for a String/cons pattern in the term and try to build a string from that point on.
// If successful, replace the term with the string.
// If not, keep as-is.
match self {
// Nil: String/nil
Term::Ref { nam } if nam == builtins::SNIL => *self = Term::str(""),
// Cons: @x (x CONS_TAG <num> <str>)
Term::Lam {
tag: Tag::Static,
pat: box Pattern::Var(Some(var_lam)),
bod:
box Term::App {
tag: Tag::Static,
fun:
box Term::App {
tag: Tag::Static,
fun:
box Term::App {
tag: Tag::Static,
fun: box Term::Var { nam: var_app },
arg: box Term::Num { val: Num::U24(SCONS_TAG) },
},
arg: box Term::Num { val: Num::U24(head) },
},
arg: tail,
},
} if var_lam == var_app => {
let head = char::from_u32(*head).unwrap_or('.');
if let Some(str) = build_string_num_scott(tail, head.to_string()) {
*self = Term::str(&str);
} else {
// Not a string term, keep as-is.
// Nil: String/nil
if let Term::Ref { nam } = self {
if nam == builtins::SNIL {
*self = Term::str("");
}
}
// Cons: @x (x CONS_TAG <num> <str>)
if let Term::Lam { tag: Tag::Static, pat, bod } = self {
if let Pattern::Var(Some(var_lam)) = pat.as_mut() {
if let Term::App { tag: Tag::Static, fun, arg: tail } = bod.as_mut() {
if let Term::App { tag: Tag::Static, fun, arg: head } = fun.as_mut() {
if let Term::App { tag: Tag::Static, fun, arg } = fun.as_mut() {
if let Term::Var { nam: var_app } = fun.as_mut() {
if let Term::Num { val: Num::U24(SCONS_TAG) } = arg.as_mut() {
if let Term::Num { val: Num::U24(head) } = head.as_mut() {
if var_lam == var_app {
let head = char::from_u32(*head).unwrap_or(char::REPLACEMENT_CHARACTER);
if let Some(str) = build_string_num_scott(tail, head.to_string()) {
*self = Term::str(&str);
} else {
// Not a string term, keep as-is.
}
}
}
}
}
}
}
}
}
// Cons: (String/cons <num> <str>)
Term::App {
tag: Tag::Static,
fun:
box Term::App {
tag: Tag::Static,
fun: box Term::Ref { nam },
arg: box Term::Num { val: Num::U24(head) },
},
arg: tail,
} if nam == builtins::SCONS => {
let head = char::from_u32(*head).unwrap_or('.');
if let Some(str) = build_string_num_scott(tail, head.to_string()) {
*self = Term::str(&str);
} else {
// Not a string term, keep as-is.
}
// Cons: (String/cons <num> <str>)
if let Term::App { tag: Tag::Static, fun, arg: tail } = self {
if let Term::App { tag: Tag::Static, fun, arg: head } = fun.as_mut() {
if let Term::Ref { nam } = fun.as_mut() {
if let Term::Num { val: Num::U24(head) } = head.as_mut() {
if nam == builtins::SCONS {
let head = char::from_u32(*head).unwrap_or(char::REPLACEMENT_CHARACTER);
if let Some(str) = build_string_num_scott(tail, head.to_string()) {
*self = Term::str(&str);
} else {
// Not a string term, keep as-is.
}
}
}
}
}
_ => (),
}
for child in self.children_mut() {
@ -84,56 +82,54 @@ impl Term {
// Search for a String/cons pattern in the term and try to build a string from that point on.
// If successful, replace the term with the string.
// If not, keep as-is.
match self {
// Nil: String/nil
Term::Ref { nam } if nam == builtins::SNIL => *self = Term::str(""),
// Cons: @* @c (c <num> <str>)
Term::Lam {
tag: Tag::Static,
pat: box Pattern::Var(None),
bod:
box Term::Lam {
tag: Tag::Static,
pat: box Pattern::Var(Some(nam_cons_lam)),
bod:
box Term::App {
tag: Tag::Static,
fun:
box Term::App {
tag: Tag::Static,
fun: box Term::Var { nam: nam_cons_app },
arg: box Term::Num { val: Num::U24(c) },
},
arg: nxt,
},
},
} if nam_cons_lam == nam_cons_app => {
let head = char::from_u32(*c).unwrap_or('.');
if let Some(str) = build_string_scott(nxt, head.to_string()) {
*self = Term::str(&str);
} else {
// Not a string term, keep as-is.
// Nil: String/nil
if let Term::Ref { nam } = self {
if nam == builtins::SNIL {
*self = Term::str("");
}
}
// Cons: @* @c (c <num> <str>)
if let Term::Lam { tag: Tag::Static, pat, bod } = self {
if let Pattern::Var(None) = pat.as_mut() {
if let Term::Lam { tag: Tag::Static, pat, bod } = bod.as_mut() {
if let Pattern::Var(Some(var_lam)) = pat.as_mut() {
if let Term::App { tag: Tag::Static, fun, arg: tail } = bod.as_mut() {
if let Term::App { tag: Tag::Static, fun, arg: head } = fun.as_mut() {
if let Term::Var { nam: var_app } = fun.as_mut() {
if let Term::Num { val: Num::U24(head) } = head.as_mut() {
if var_lam == var_app {
let head = char::from_u32(*head).unwrap_or(char::REPLACEMENT_CHARACTER);
if let Some(str) = build_string_scott(tail, head.to_string()) {
*self = Term::str(&str);
} else {
// Not a string term, keep as-is.
}
}
}
}
}
}
}
}
}
// Cons: (String/cons <num> <str>)
Term::App {
tag: Tag::Static,
fun:
box Term::App {
tag: Tag::Static,
fun: box Term::Ref { nam },
arg: box Term::Num { val: Num::U24(c) },
},
arg: nxt,
} if nam == builtins::SCONS => {
let head = char::from_u32(*c).unwrap_or('.');
if let Some(str) = build_string_scott(nxt, head.to_string()) {
*self = Term::str(&str);
} else {
// Not a string term, keep as-is.
}
// Cons: (String/cons <num> <str>)
if let Term::App { tag: Tag::Static, fun, arg: tail } = self {
if let Term::App { tag: Tag::Static, fun, arg: head } = fun.as_mut() {
if let Term::Ref { nam } = fun.as_mut() {
if let Term::Num { val: Num::U24(head) } = head.as_mut() {
if nam == builtins::SCONS {
let head = char::from_u32(*head).unwrap_or(char::REPLACEMENT_CHARACTER);
if let Some(str) = build_string_num_scott(tail, head.to_string()) {
*self = Term::str(&str);
} else {
// Not a string term, keep as-is.
}
}
}
}
}
_ => (),
}
for child in self.children_mut() {
@ -145,110 +141,102 @@ impl Term {
fn build_string_num_scott(term: &Term, mut s: String) -> Option<String> {
maybe_grow(|| {
match term {
// Nil: String/nil
Term::Ref { nam } if nam == builtins::SNIL => Some(s),
// Cons: @x (x CONS_TAG <num> <str>)
Term::Lam {
tag: Tag::Static,
pat: box Pattern::Var(Some(var_lam)),
bod:
box Term::App {
tag: Tag::Static,
fun:
box Term::App {
tag: Tag::Static,
fun:
box Term::App {
tag: Tag::Static,
fun: box Term::Var { nam: var_app },
arg: box Term::Num { val: Num::U24(SCONS_TAG) },
},
arg: box Term::Num { val: Num::U24(head) },
},
arg: tail,
},
} if var_lam == var_app => {
// New string character, append and recurse
let head = char::from_u32(*head).unwrap_or('.');
s.push(head);
build_string_num_scott(tail, s)
}
// Cons: (String/cons <num> <str>)
Term::App {
tag: Tag::Static,
fun:
box Term::App {
tag: Tag::Static,
fun: box Term::Ref { nam },
arg: box Term::Num { val: Num::U24(head) },
},
arg: tail,
} if nam == builtins::SCONS => {
// New string character, append and recurse
let head = char::from_u32(*head).unwrap_or('.');
s.push(head);
build_string_num_scott(tail, s)
}
_ => {
// Not a string term, stop
None
// Nil: String/nil
if let Term::Ref { nam } = term {
if nam == builtins::SNIL {
return Some(s);
}
}
// Cons: @x (x CONS_TAG <num> <str>)
if let Term::Lam { tag: Tag::Static, pat, bod } = term {
if let Pattern::Var(Some(var_lam)) = pat.as_ref() {
if let Term::App { tag: Tag::Static, fun, arg: tail } = bod.as_ref() {
if let Term::App { tag: Tag::Static, fun, arg: head } = fun.as_ref() {
if let Term::App { tag: Tag::Static, fun, arg } = fun.as_ref() {
if let Term::Var { nam: var_app } = fun.as_ref() {
if let Term::Num { val: Num::U24(SCONS_TAG) } = arg.as_ref() {
if let Term::Num { val: Num::U24(head) } = head.as_ref() {
if var_lam == var_app {
// New string character, append and recurse
let head = char::from_u32(*head).unwrap_or(char::REPLACEMENT_CHARACTER);
s.push(head);
return build_string_num_scott(tail, s);
}
}
}
}
}
}
}
}
}
// Cons: (String/cons <num> <str>)
if let Term::App { tag: Tag::Static, fun, arg: tail } = term {
if let Term::App { tag: Tag::Static, fun, arg: head } = fun.as_ref() {
if let Term::Ref { nam } = fun.as_ref() {
if let Term::Num { val: Num::U24(head) } = head.as_ref() {
if nam == builtins::SCONS {
// New string character, append and recurse
let head = char::from_u32(*head).unwrap_or(char::REPLACEMENT_CHARACTER);
s.push(head);
return build_string_num_scott(tail, s);
}
}
}
}
}
// Not a string term, stop
None
})
}
fn build_string_scott(term: &Term, mut s: String) -> Option<String> {
maybe_grow(|| {
match term {
// Nil: String/nil
Term::Ref { nam } if nam == builtins::SNIL => Some(s),
// Cons: @* @c (c <num> <str>)
Term::Lam {
tag: Tag::Static,
pat: box Pattern::Var(None),
bod:
box Term::Lam {
tag: Tag::Static,
pat: box Pattern::Var(Some(nam_cons_lam)),
bod:
box Term::App {
tag: Tag::Static,
fun:
box Term::App {
tag: Tag::Static,
fun: box Term::Var { nam: nam_cons_app },
arg: box Term::Num { val: Num::U24(c) },
},
arg: nxt,
},
},
} if nam_cons_lam == nam_cons_app => {
// New string character, append and recurse
let head = char::from_u32(*c).unwrap_or('.');
s.push(head);
build_string_scott(nxt, s)
}
// Cons: (String/cons <num> <str>)
Term::App {
tag: Tag::Static,
fun:
box Term::App {
tag: Tag::Static,
fun: box Term::Ref { nam },
arg: box Term::Num { val: Num::U24(c) },
},
arg: nxt,
} if nam == builtins::SCONS => {
// New string character, append and recurse
let head = char::from_u32(*c).unwrap_or('.');
s.push(head);
build_string_scott(nxt, s)
}
_ => {
// Not a string term, stop
None
// Nil: String/nil
if let Term::Ref { nam } = term {
if nam == builtins::SNIL {
return Some(s);
}
}
// Cons: @* @c (c <num> <str>)
if let Term::Lam { tag: Tag::Static, pat, bod } = term {
if let Pattern::Var(None) = pat.as_ref() {
if let Term::Lam { tag: Tag::Static, pat, bod } = bod.as_ref() {
if let Pattern::Var(Some(var_lam)) = pat.as_ref() {
if let Term::App { tag: Tag::Static, fun, arg: tail } = bod.as_ref() {
if let Term::App { tag: Tag::Static, fun, arg: head } = fun.as_ref() {
if let Term::Var { nam: var_app } = fun.as_ref() {
if let Term::Num { val: Num::U24(head) } = head.as_ref() {
if var_lam == var_app {
// New string character, append and recurse
let head = char::from_u32(*head).unwrap_or(char::REPLACEMENT_CHARACTER);
s.push(head);
return build_string_scott(tail, s);
}
}
}
}
}
}
}
}
}
// Cons: (String/cons <num> <str>)
if let Term::App { tag: Tag::Static, fun, arg: tail } = term {
if let Term::App { tag: Tag::Static, fun, arg: head } = fun.as_ref() {
if let Term::Ref { nam } = fun.as_ref() {
if let Term::Num { val: Num::U24(head) } = head.as_ref() {
if nam == builtins::SCONS {
// New string character, append and recurse
let head = char::from_u32(*head).unwrap_or(char::REPLACEMENT_CHARACTER);
s.push(head);
return build_string_scott(tail, s);
}
}
}
}
}
// Not a string term, stop
None
})
}

View File

@ -1,20 +1,19 @@
use crate::{
hvm::ast::{Book, Net, Tree},
maybe_grow,
};
use super::tree_children;
use crate::maybe_grow;
use hvm::ast::{Book, Net, Tree};
use std::collections::{HashMap, HashSet};
pub fn add_recursive_priority(book: &mut Book) {
// Direct dependencies
let deps = book.iter().map(|(nam, net)| (nam.clone(), dependencies(net))).collect::<HashMap<_, _>>();
let deps = book.defs.iter().map(|(nam, net)| (nam.clone(), dependencies(net))).collect::<HashMap<_, _>>();
// Recursive cycles
let cycles = cycles(&deps);
for cycle in cycles {
// For each function in the cycle, if there are redexes with the
// next ref in the cycle, add a priority to one of those redexes.
for i in 0 .. cycle.len() {
let cur = book.get_mut(&cycle[i]).unwrap();
for i in 0..cycle.len() {
let cur = book.defs.get_mut(&cycle[i]).unwrap();
let nxt = &cycle[(i + 1) % cycle.len()];
add_priority_next_in_cycle(cur, nxt);
}
@ -25,7 +24,7 @@ fn add_priority_next_in_cycle(net: &mut Net, nxt: &String) {
let mut count = 0;
// Count the number of recursive refs
for (_, a, b) in net.redexes.iter() {
for (_, a, b) in net.rbag.iter() {
if let Tree::Ref { nam } = a {
if nam == nxt {
count += 1;
@ -40,7 +39,7 @@ fn add_priority_next_in_cycle(net: &mut Net, nxt: &String) {
// If there are more than one recursive ref, add a priority to them.
if count > 1 {
for (pri, a, b) in net.redexes.iter_mut().rev() {
for (pri, a, b) in net.rbag.iter_mut().rev() {
if let Tree::Ref { nam } = a {
if nam == nxt {
*pri = true;
@ -82,7 +81,7 @@ fn find_cycles(
// Check if the current ref is already in the stack, which indicates a cycle.
if let Some(cycle_start) = stack.iter().position(|n| n == nam) {
// If found, add the cycle to the cycles vector.
cycles.push(stack[cycle_start ..].to_vec());
cycles.push(stack[cycle_start..].to_vec());
return;
}
// If the ref has not been visited yet, mark it as visited.
@ -105,7 +104,7 @@ fn find_cycles(
fn dependencies(net: &Net) -> HashSet<String> {
let mut deps = HashSet::new();
dependencies_tree(&net.root, &mut deps);
for (_, a, b) in &net.redexes {
for (_, a, b) in &net.rbag {
dependencies_tree(a, &mut deps);
dependencies_tree(b, &mut deps);
}
@ -116,7 +115,7 @@ fn dependencies_tree(tree: &Tree, deps: &mut HashSet<String>) {
if let Tree::Ref { nam, .. } = tree {
deps.insert(nam.clone());
} else {
for subtree in tree.children() {
for subtree in tree_children(tree) {
dependencies_tree(subtree, deps);
}
}

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

@ -1,13 +1,13 @@
use super::tree_children;
use crate::{
diagnostics::{Diagnostics, WarningType, ERR_INDENT_SIZE},
fun::transform::definition_merge::MERGE_SEPARATOR,
maybe_grow,
};
use hvm::ast::{Book, Tree};
use indexmap::{IndexMap, IndexSet};
use std::fmt::Debug;
use super::ast::{Book, Tree};
type Ref = String;
type Stack<T> = Vec<T>;
type RefSet = IndexSet<Ref>;
@ -83,7 +83,7 @@ impl Graph {
// Check if the current ref is already in the stack, which indicates a cycle.
if let Some(cycle_start) = stack.iter().position(|n| n == r#ref) {
// If found, add the cycle to the cycles vector.
cycles.push(stack[cycle_start ..].to_vec());
cycles.push(stack[cycle_start..].to_vec());
return;
}
@ -109,13 +109,9 @@ impl Graph {
fn collect_refs(current: Ref, tree: &Tree, graph: &mut Graph) {
maybe_grow(|| match tree {
Tree::Ref { nam, .. } => graph.add(current, nam.clone()),
Tree::Ctr { ports, .. } => {
if let Some(last) = ports.last() {
collect_refs(current, last, graph);
}
}
Tree::Con { fst: _, snd } => collect_refs(current.clone(), snd, graph),
tree => {
for subtree in tree.children() {
for subtree in tree_children(tree) {
collect_refs(current.clone(), subtree, graph);
}
}
@ -126,12 +122,12 @@ impl From<&Book> for Graph {
fn from(book: &Book) -> Self {
let mut graph = Self::new();
for (r#ref, net) in book.iter() {
for (r#ref, net) in book.defs.iter() {
// Collect active refs from the root.
collect_refs(r#ref.clone(), &net.root, &mut graph);
// Collect active refs from redexes.
for (_, left, right) in net.redexes.iter() {
for (_, left, right) in net.rbag.iter() {
if let Tree::Ref { nam, .. } = left {
graph.add(r#ref.clone(), nam.clone());
}
@ -171,7 +167,7 @@ fn combinations_from_merges(cycle: Vec<Ref>) -> Vec<Vec<Ref>> {
for r#ref in cycle {
if let Some(index) = r#ref.find(MERGE_SEPARATOR) {
let (left, right) = r#ref.split_at(index);
let right = &right[MERGE_SEPARATOR.len() ..]; // skip merge separator
let right = &right[MERGE_SEPARATOR.len()..]; // skip merge separator
let mut new_combinations = Vec::new();
for combination in &combinations {
let mut left_comb = combination.clone();

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -178,7 +178,7 @@ fn go_order_kwargs(
);
}
let mut kwargs: IndexMap<Name, Expr> = IndexMap::from_iter(kwargs.drain(..));
let remaining_names = &names[args.len() ..];
let remaining_names = &names[args.len()..];
for name in remaining_names {
if let Some(arg) = kwargs.shift_remove(name) {
args.push(arg);

View File

@ -44,7 +44,7 @@ impl<'a> Parser<'a> for PyParser<'a> {
/// Override to have our own error message.
fn consume(&mut self, text: &str) -> ParseResult<()> {
self.skip_trivia();
if self.input().get(*self.index() ..).unwrap_or_default().starts_with(text) {
if self.input().get(*self.index()..).unwrap_or_default().starts_with(text) {
*self.index() += text.len();
Ok(())
} else {
@ -532,6 +532,21 @@ impl<'a> PyParser<'a> {
if nxt_indent != *indent {
return self.expected_indent(*indent, nxt_indent);
}
let mut elifs = Vec::new();
while self.try_parse_keyword("elif") {
let cond = self.parse_expr(true)?;
self.skip_trivia_inline();
self.consume_exactly(":")?;
indent.enter_level();
self.consume_indent_exactly(*indent)?;
let (then, nxt_indent) = self.parse_statement(indent)?;
indent.exit_level();
if nxt_indent != *indent {
return self.expected_indent(*indent, nxt_indent);
}
elifs.push((cond, then));
}
self.parse_keyword("else")?;
self.skip_trivia_inline();
self.consume_exactly(":")?;
@ -539,6 +554,13 @@ impl<'a> PyParser<'a> {
self.consume_indent_exactly(*indent)?;
let (otherwise, nxt_indent) = self.parse_statement(indent)?;
let otherwise = elifs.into_iter().fold(otherwise, |acc, (cond, then)| Stmt::If {
cond: Box::new(cond),
then: Box::new(then),
otherwise: Box::new(acc),
nxt: None,
});
indent.exit_level();
if nxt_indent == *indent {
let (nxt, nxt_indent) = self.parse_statement(indent)?;

View File

@ -59,14 +59,17 @@ impl Stmt {
};
let term = fun::Term::Let {
pat: Box::new(fun::Pattern::Var(Some(map.clone()))),
val: Box::new(fun::Term::call(fun::Term::Ref { nam: fun::Name::new("Map/set") }, [
fun::Term::Var { nam: map },
key.to_fun(),
val.to_fun(),
])),
val: Box::new(fun::Term::call(
fun::Term::Ref { nam: fun::Name::new("Map/set") },
[fun::Term::Var { nam: map }, key.to_fun(), val.to_fun()],
)),
nxt: Box::new(nxt),
};
if let Some(pat) = nxt_pat { StmtToFun::Assign(pat, term) } else { StmtToFun::Return(term) }
if let Some(pat) = nxt_pat {
StmtToFun::Assign(pat, term)
} else {
StmtToFun::Return(term)
}
}
Stmt::Assign { pat: AssignPattern::MapSet(..), val: _, nxt: None } => {
return Err("Branch ends with map assignment.".to_string());
@ -79,7 +82,11 @@ impl Stmt {
StmtToFun::Assign(pat, term) => (Some(pat), term),
};
let term = fun::Term::Let { pat: Box::new(pat), val: Box::new(val), nxt: Box::new(nxt) };
if let Some(pat) = nxt_pat { StmtToFun::Assign(pat, term) } else { StmtToFun::Return(term) }
if let Some(pat) = nxt_pat {
StmtToFun::Assign(pat, term)
} else {
StmtToFun::Return(term)
}
}
Stmt::Assign { pat, val, nxt: None } => {
let pat = pat.into_fun();
@ -100,7 +107,11 @@ impl Stmt {
}),
nxt: Box::new(nxt),
};
if let Some(pat) = nxt_pat { StmtToFun::Assign(pat, term) } else { StmtToFun::Return(term) }
if let Some(pat) = nxt_pat {
StmtToFun::Assign(pat, term)
} else {
StmtToFun::Return(term)
}
}
Stmt::If { cond, then, otherwise, nxt } => {
let (pat, then, else_) = match (then.into_fun()?, otherwise.into_fun()?) {
@ -266,7 +277,11 @@ impl Stmt {
};
let term =
fun::Term::Ask { pat: Box::new(pat.into_fun()), val: Box::new(val.to_fun()), nxt: Box::new(nxt) };
if let Some(pat) = nxt_pat { StmtToFun::Assign(pat, term) } else { StmtToFun::Return(term) }
if let Some(pat) = nxt_pat {
StmtToFun::Assign(pat, term)
} else {
StmtToFun::Return(term)
}
}
Stmt::Open { typ, var, nxt } => {
let (nxt_pat, nxt) = match nxt.into_fun()? {
@ -274,7 +289,11 @@ impl Stmt {
StmtToFun::Assign(pat, term) => (Some(pat), term),
};
let term = fun::Term::Open { typ, var, bod: Box::new(nxt) };
if let Some(pat) = nxt_pat { StmtToFun::Assign(pat, term) } else { StmtToFun::Return(term) }
if let Some(pat) = nxt_pat {
StmtToFun::Assign(pat, term)
} else {
StmtToFun::Return(term)
}
}
Stmt::Use { nam, val, nxt } => {
let (nxt_pat, nxt) = match nxt.into_fun()? {
@ -282,7 +301,11 @@ impl Stmt {
StmtToFun::Assign(pat, term) => (Some(pat), term),
};
let term = fun::Term::Use { nam: Some(nam), val: Box::new(val.to_fun()), nxt: Box::new(nxt) };
if let Some(pat) = nxt_pat { StmtToFun::Assign(pat, term) } else { StmtToFun::Return(term) }
if let Some(pat) = nxt_pat {
StmtToFun::Assign(pat, term)
} else {
StmtToFun::Return(term)
}
}
Stmt::Return { term } => StmtToFun::Return(term.to_fun()),
Stmt::Err => unreachable!(),
@ -332,9 +355,10 @@ impl Expr {
const ITER_TAIL: &str = "%iter.tail";
const ITER_HEAD: &str = "%iter.head";
let cons_branch = fun::Term::call(fun::Term::r#ref(LCONS), [term.to_fun(), fun::Term::Var {
nam: Name::new(ITER_TAIL),
}]);
let cons_branch = fun::Term::call(
fun::Term::r#ref(LCONS),
[term.to_fun(), fun::Term::Var { nam: Name::new(ITER_TAIL) }],
);
let cons_branch = if let Some(cond) = cond {
fun::Term::Swt {
arg: Box::new(cond.to_fun()),
@ -391,7 +415,11 @@ fn wrap_nxt_assign_stmt(
StmtToFun::Assign(pat, term) => (Some(pat), term),
};
let term = fun::Term::Let { pat: Box::new(pat), val: Box::new(term), nxt: Box::new(nxt) };
if let Some(pat) = nxt_pat { Ok(StmtToFun::Assign(pat, term)) } else { Ok(StmtToFun::Return(term)) }
if let Some(pat) = nxt_pat {
Ok(StmtToFun::Assign(pat, term))
} else {
Ok(StmtToFun::Return(term))
}
} else {
Err("Statement ends with return but is not at end of function.".to_string())
}

View File

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

View File

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

View File

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

View File

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

38
src/utils.rs Normal file
View File

@ -0,0 +1,38 @@
/// A macro for creating iterators that can have statically known
/// different types. Useful for iterating over tree children, where
/// each tree node variant yields a different iterator type.
#[macro_export]
macro_rules! multi_iterator {
($Iter:ident { $($Variant:ident),* $(,)? }) => {
#[derive(Debug, Clone)]
enum $Iter<$($Variant),*> {
$($Variant { iter: $Variant }),*
}
impl<$($Variant),*> $Iter<$($Variant),*> {
$(
#[allow(non_snake_case)]
fn $Variant(iter: impl IntoIterator<IntoIter = $Variant>) -> Self {
$Iter::$Variant { iter: iter.into_iter() }
}
)*
}
impl<T, $($Variant: Iterator<Item = T>),*> Iterator for $Iter<$($Variant),*> {
type Item = T;
fn next(&mut self) -> Option<T> {
match self { $($Iter::$Variant { iter } => iter.next()),* }
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self { $($Iter::$Variant { iter } => iter.size_hint()),* }
}
}
impl<T, $($Variant: DoubleEndedIterator<Item = T>),*> DoubleEndedIterator for $Iter<$($Variant),*> {
fn next_back(&mut self) -> Option<T> {
match self { $($Iter::$Variant { iter } => iter.next_back()),* }
}
}
};
}

View File

@ -2,8 +2,8 @@ use bend::{
compile_book, desugar_book,
diagnostics::{Diagnostics, DiagnosticsConfig, Severity},
fun::{load_book::do_parse_book, net_to_term::net_to_term, term_to_net::Labels, Book, Ctx, Name, Term},
hvm,
net::hvmc_to_net::hvmc_to_net,
hvm::display_hvm_book,
net::hvm_to_net::hvm_to_net,
run_book, AdtEncoding, CompileOpts, RunOpts,
};
use insta::assert_snapshot;
@ -13,7 +13,6 @@ use std::{
fmt::Write,
io::Read,
path::{Path, PathBuf},
str::FromStr,
};
use stdext::function_name;
use walkdir::WalkDir;
@ -33,7 +32,7 @@ fn run_single_golden_test(path: &Path, run: &[&RunFn]) -> Result<(), String> {
let file_name = path.to_str().and_then(|path| path.rsplit_once(TESTS_PATH)).unwrap().1;
// unfortunately we need to do this
let file_path = format!("{}{}", &TESTS_PATH[1 ..], file_name);
let file_path = format!("{}{}", &TESTS_PATH[1..], file_name);
let file_path = Path::new(&file_path);
let mut results: HashMap<&Path, Vec<String>> = HashMap::new();
@ -109,7 +108,7 @@ fn compile_file() {
let diagnostics_cfg = DiagnosticsConfig { unused_definition: Severity::Allow, ..Default::default() };
let res = compile_book(&mut book, compile_opts, diagnostics_cfg, None)?;
Ok(format!("{}{}", res.diagnostics, res.core_book))
Ok(format!("{}{}", res.diagnostics, display_hvm_book(&res.hvm_book)))
})
}
@ -125,7 +124,7 @@ fn compile_file_o_all() {
};
let res = compile_book(&mut book, opts, diagnostics_cfg, None)?;
Ok(format!("{}{}", res.diagnostics, res.core_book))
Ok(format!("{}{}", res.diagnostics, display_hvm_book(&res.hvm_book)))
})
}
@ -136,7 +135,7 @@ fn compile_file_o_no_all() {
let compile_opts = CompileOpts::default().set_no_all();
let diagnostics_cfg = DiagnosticsConfig::default();
let res = compile_book(&mut book, compile_opts, diagnostics_cfg, None)?;
Ok(format!("{}", res.core_book))
Ok(format!("{}", display_hvm_book(&res.hvm_book)))
})
}
@ -161,24 +160,27 @@ fn linear_readback() {
#[test]
fn run_file() {
run_golden_test_dir_multiple(function_name!(), &[(&|code, path| {
let _guard = RUN_MUTEX.lock().unwrap();
let book = do_parse_book(code, path, Book::builtins())?;
let diagnostics_cfg = DiagnosticsConfig {
unused_definition: Severity::Allow,
..DiagnosticsConfig::new(Severity::Error, true)
};
let run_opts = RunOpts::default();
run_golden_test_dir_multiple(
function_name!(),
&[(&|code, path| {
let _guard = RUN_MUTEX.lock().unwrap();
let book = do_parse_book(code, path, Book::builtins())?;
let diagnostics_cfg = DiagnosticsConfig {
unused_definition: Severity::Allow,
..DiagnosticsConfig::new(Severity::Error, true)
};
let run_opts = RunOpts::default();
let mut res = String::new();
let mut res = String::new();
for adt_encoding in [AdtEncoding::NumScott, AdtEncoding::Scott] {
let compile_opts = CompileOpts { adt_encoding, ..CompileOpts::default() };
let (term, _, diags) = run_book_simple(book.clone(), run_opts, compile_opts, diagnostics_cfg, None)?;
res.push_str(&format!("{adt_encoding}:\n{diags}{term}\n\n"));
}
Ok(res)
})])
for adt_encoding in [AdtEncoding::NumScott, AdtEncoding::Scott] {
let compile_opts = CompileOpts { adt_encoding, ..CompileOpts::default() };
let (term, _, diags) = run_book_simple(book.clone(), run_opts, compile_opts, diagnostics_cfg, None)?;
res.push_str(&format!("{adt_encoding}:\n{diags}{term}\n\n"));
}
Ok(res)
})],
)
}
#[test]
@ -205,9 +207,10 @@ fn run_lazy() {
#[test]
fn readback_lnet() {
run_golden_test_dir(function_name!(), &|code, _| {
let net = hvm::ast::Net::from_str(code)?;
let mut p = hvm::ast::CoreParser::new(code);
let net = p.parse_net()?;
let book = Book::default();
let compat_net = hvmc_to_net(&net);
let compat_net = hvm_to_net(&net);
let mut diags = Diagnostics::default();
let term = net_to_term(&compat_net, &book, &Labels::default(), false, &mut diags);
Ok(format!("{}{}", diags, term))
@ -339,7 +342,7 @@ fn compile_entrypoint() {
book.entrypoint = Some(Name::new("foo"));
let diagnostics_cfg = DiagnosticsConfig { ..DiagnosticsConfig::new(Severity::Error, true) };
let res = compile_book(&mut book, CompileOpts::default(), diagnostics_cfg, None)?;
Ok(format!("{}{}", res.diagnostics, res.core_book))
Ok(format!("{}{}", res.diagnostics, display_hvm_book(&res.hvm_book)))
})
}
@ -386,36 +389,40 @@ fn mutual_recursion() {
let mut book = do_parse_book(code, path, Book::builtins())?;
let opts = CompileOpts { merge: true, ..CompileOpts::default() };
let res = compile_book(&mut book, opts, diagnostics_cfg, None)?;
Ok(format!("{}{}", res.diagnostics, res.core_book))
Ok(format!("{}{}", res.diagnostics, display_hvm_book(&res.hvm_book)))
})
}
#[test]
#[ignore = "while IO is not implemented for hvm32"]
fn io() {
run_golden_test_dir_multiple(function_name!(), &[
/* (&|code, path| {
let _guard = RUN_MUTEX.lock().unwrap();
let book = do_parse_book(code, path)?;
let compile_opts = CompileOpts::default_lazy();
let diagnostics_cfg = DiagnosticsConfig::default_lazy();
let Output { status, stdout, stderr } =
run_book(book, None, RunOpts::lazy(), compile_opts, diagnostics_cfg, None)?;
let stderr = String::from_utf8_lossy(&stderr);
let status = if !status.success() { format!("\n{status}") } else { String::new() };
let stdout = String::from_utf8_lossy(&stdout);
Ok(format!("Lazy mode:\n{}{}{}", stderr, status, stdout))
}), */
(&|code, path| {
let _guard = RUN_MUTEX.lock().unwrap();
let book = do_parse_book(code, path, Book::builtins())?;
let compile_opts = CompileOpts::default();
let diagnostics_cfg = DiagnosticsConfig::default();
let (term, _, diags) = run_book_simple(book, RunOpts::default(), compile_opts, diagnostics_cfg, None)?;
let res = format!("{diags}{term}");
Ok(format!("Strict mode:\n{res}"))
}),
])
run_golden_test_dir_multiple(
function_name!(),
&[
/* (&|code, path| {
let _guard = RUN_MUTEX.lock().unwrap();
let book = do_parse_book(code, path)?;
let compile_opts = CompileOpts::default_lazy();
let diagnostics_cfg = DiagnosticsConfig::default_lazy();
let Output { status, stdout, stderr } =
run_book(book, None, RunOpts::lazy(), compile_opts, diagnostics_cfg, None)?;
let stderr = String::from_utf8_lossy(&stderr);
let status = if !status.success() { format!("\n{status}") } else { String::new() };
let stdout = String::from_utf8_lossy(&stdout);
Ok(format!("Lazy mode:\n{}{}{}", stderr, status, stdout))
}), */
(&|code, path| {
let _guard = RUN_MUTEX.lock().unwrap();
let book = do_parse_book(code, path, Book::builtins())?;
let compile_opts = CompileOpts::default();
let diagnostics_cfg = DiagnosticsConfig::default();
let (term, _, diags) =
run_book_simple(book, RunOpts::default(), compile_opts, diagnostics_cfg, None)?;
let res = format!("{diags}{term}");
Ok(format!("Strict mode:\n{res}"))
}),
],
)
}
#[test]
@ -460,6 +467,6 @@ fn scott_triggers_unused() {
let diagnostics_cfg =
DiagnosticsConfig { unused_definition: Severity::Error, ..DiagnosticsConfig::default() };
let res = compile_book(&mut book, opts, diagnostics_cfg, None)?;
Ok(format!("{}{}", res.diagnostics, res.core_book))
Ok(format!("{}{}", res.diagnostics, display_hvm_book(&res.hvm_book)))
})
}

View File

@ -0,0 +1,16 @@
def main:
cond1 = 1 == 2
cond2 = 2 < 1
cond3 = 3 > 2
cond4 = 2 == 2
if cond1:
res = 1
elif cond2:
res = 2
elif cond3:
res = 3
elif cond4:
res = 4
else:
res = 0
return res

View File

@ -0,0 +1,5 @@
def main:
if 1 == 1:
return 0
elif 2 == 2:
return 1

View File

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

View File

@ -0,0 +1,18 @@
---
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/elif.bend
---
@main = d
& @main__C3 ~ (a (b (c d)))
& $(1 a) ~ [<2]
& $(2 b) ~ [>3]
& $(2 c) ~ [=2]
@main__C0 = (?((0 (* 2)) a) a)
@main__C1 = (a (?((@main__C0 (* (* 3))) (a b)) b))
@main__C2 = (a (b (?((@main__C1 (* (* (* 4)))) (a (b c))) c)))
@main__C3 = a
& $(2 ?((@main__C2 (* (* (* (* 1))))) a)) ~ [=1]

View File

@ -0,0 +1,8 @@
---
source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/elif_no_else.bend
---
Errors:
In tests/golden_tests/compile_file/elif_no_else.bend :
Indentation error. Expected 2 spaces, got end-of-input.
 6 |  

View File

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

View File

@ -4,6 +4,6 @@ input_file: tests/golden_tests/compile_file/wrong_nums.bend
---
Errors:
In tests/golden_tests/compile_file/wrong_nums.bend :
- expected: ')'
- expected: valid binary digit
- detected:
 1 | main = (+ 0b012345 0FA)
 1 | main = (+ 0b012345 0FA)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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