[sc-630] Rename the crate to bend and update docs for it

This commit is contained in:
Nicolas Abril 2024-05-06 21:14:23 +02:00
parent 02e0c3ba26
commit cf680f559d
16 changed files with 122 additions and 173 deletions

38
Cargo.lock generated
View File

@ -79,6 +79,25 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
[[package]]
name = "bend"
version = "0.1.0"
dependencies = [
"TSPL 0.0.9 (git+https://github.com/developedby/TSPL.git?branch=fix-hvml-bugs)",
"clap",
"highlight_error",
"hvm-core",
"indexmap",
"insta",
"interner",
"itertools",
"loaned",
"parking_lot",
"stacker",
"stdext",
"walkdir",
]
[[package]]
name = "bitflags"
version = "2.5.0"
@ -205,25 +224,6 @@ dependencies = [
"thiserror",
]
[[package]]
name = "hvm-lang"
version = "0.1.0"
dependencies = [
"TSPL 0.0.9 (git+https://github.com/developedby/TSPL.git?branch=fix-hvml-bugs)",
"clap",
"highlight_error",
"hvm-core",
"indexmap",
"insta",
"interner",
"itertools",
"loaned",
"parking_lot",
"stacker",
"stdext",
"walkdir",
]
[[package]]
name = "indexmap"
version = "2.2.6"

View File

@ -1,14 +1,14 @@
[package]
name = "hvm-lang"
name = "bend"
version = "0.1.0"
edition = "2021"
[lib]
name = "hvml"
name = "bend"
path = "src/lib.rs"
[[bin]]
name = "hvml"
name = "bend"
path = "src/main.rs"
required-features = ["cli"]

View File

@ -19,6 +19,7 @@
"Dall",
"datatypes",
"Deque",
"destructures",
"desugared",
"desugars",
"dref",
@ -29,7 +30,6 @@
"hexdigit",
"hvm's",
"hvmc",
"hvml",
"indexmap",
"inet",
"inets",
@ -40,6 +40,7 @@
"insta",
"interner",
"itertools",
"ITRS",
"kwargs",
"lcons",
"linearization",
@ -58,10 +59,11 @@
"opre",
"oprune",
"oref",
"parallelizable",
"peekable",
"postcondition",
"prec",
"powi",
"prec",
"readback",
"recursively",
"redex",
@ -77,6 +79,7 @@
"scrutinee",
"snil",
"stdext",
"struct",
"subcmd",
"submatch",
"subpattern",
@ -94,6 +97,13 @@
"walkdir",
"wopts"
],
"files": ["**/*.rs", "**/*.md"],
"ignoreRegExpList": ["HexValues", "/λ/g", "/-O/g"]
"files": [
"**/*.rs",
"**/*.md"
],
"ignoreRegExpList": [
"HexValues",
"/λ/g",
"/-O/g"
]
}

View File

@ -1,59 +0,0 @@
# Automatic vectorization with tagged lambdas
We have seen in [Dups and Sups](dups-and-sups.md) that duplications and superpositions can have labels. In HVM, lambdas and applications can have labels too.
Tagged applications will only annihilate lambdas with the same tag.
```rs
// V application's tag
#A(#A λx(body) arg)
// ^ lambda's tag
// The tag must go before the term.
// This reduces to
x = arg; body
```
For example, data types can be encoded as tagged lambdas:
```rs
// data Bool = T | F
T = #Bool λt #Bool λf t
F = #Bool λt #Bool λf f
// data List = (Cons x xs) | Nil
Cons = λx λxs #List λc #List λn #List.Cons.xs(#List.Cons.x(c x) xs)
Nil = #List λc #List λn n
```
When encoding the pattern matching, the application can then use the same label:
```rs
// not = λbool match bool { T: (F) F: (T) }
not = λbool #Bool(bool F T)
```
In fact, `match` is syntax sugar for a tagged application like the one above. This means that it is not possible to match without using tagged applications.
When an application and a lambda with different tags interact, the application "commutes" through the lambda instead of beta-reducing it. Here is how it works, roughly:
```rs
#A (#B λx (b x) a)
// Reduces to
#B λc #A((b #A λ$d c) #B(a $d))
```
This reduction can be hard to grasp, but an accurate way to understand it is that "the application goes through the lambda".
This allows, in some limited scenarios, automatic vectorization. See "limitations" for a description of the limitations.
```rs
// vectorizes to: (Cons F (Cons T (Cons F Nil)))
main = (not (Cons T (Cons F (Cons T Nil))))
```
This works because the `Bool`-tagged application in `not` passes through the `List`-tagged lambdas in `Cons` until it gets to `T` and `F`.
The tagged lambda and applications are compiled to `inet` nodes with different tag values for each data type. This allows them to commute, read [HVM-Core](https://github.com/HigherOrderCO/hvm-core/tree/main#language) to learn more about it.
### Limitations
To be able to vectorize as described here:
- The function must not be recursive
- There must not be labels in common between the function and what you want to vectorize over

View File

@ -1,33 +1,41 @@
# CLI arguments
It's possible to pass arguments to a program executed with `hvml run`:
It's possible to pass arguments to a program executed with `bend run` or `bend norm`:
```sh
hvml run <Path to program> [Arguments in expression form]...
bend run <Path to program> [Arguments in expression form]...
```
It accepts any expression that would also be valid inside an hvm-lang function.
It accepts any expression that would also be valid inside a bend function.
Arguments are passed to programs by applying them to the entrypoint function:
```js
main x1 x2 x3 = (MainBody x1 x2 x3)
```py
// Core syntax
main(x1, x2, x3):
MainBody(x1 x2 x3)
// Calling with `hvml run <file> arg1 arg2 arg3 argN`, it becomes:
main = (λx1 λx2 λx3 (MainBody x1 x2 x3) arg1 arg2 arg3 argN)
// Calling with `bend run <file> arg1 arg2 arg3 argN`, it becomes (in core syntax):
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.
You can even pass more arguments than the function expects, although that can lead to unexpected results.
```rust
// Can receive 2 CLI arguments
main x y = (+ x y)
// Can't receive CLI arguments
main = λx λy (+ x y)
// Expects 2 CLI arguments
def main(x, y):
{x - y, y - x}
// Calling with just one argument
hvml run <path> 5
λa (+ a 5)
bend norm <path> +5
λa {(- a 5) (- a +5)}
// Calling with two argument
bend norm <path> +5 +3
{+2 -2}
// Calling with three argument
// In this case, the third argument doesn't do anything due to the underlying interaction rules.
bend norm <path> +5 +3 +1
{+2 -2}
```

View File

@ -2,10 +2,10 @@
How are terms compiled to interaction net nodes?
HVM-Core has a bunch of useful nodes to write IC programs.
HVM has a bunch of useful nodes to write IC programs.
Every node contains one `main` port `0` and two `auxiliary` ports, `1` and `2`.
There are 6 kinds of nodes, Eraser, Constructor, Reference, Number, Operation and Match.
There are 7 kinds of nodes, Eraser, Constructor, Duplicator, Reference, Number, Operation and Match.
A lambda `λx x` compiles into a Constructor node.
An application `((λx x) (λx x))` also compiles into a Constructor node.
@ -23,13 +23,10 @@ Points to the lambda variable Points to the argument
When reading back, if we visit a Constructor via port 0 then we know it's a lambda, and if we visit it via port 2 it's an application.
- The `Number` node uses the label to store it's number.
- An `Op2` node uses the label to store it's operation.
- And a `Constructor` node can have a label too! This is used for `dup` and [lambda tags](automatic-vectorization-with-tagged-lambdas.md.
- An `Operation` node uses the label to store it's operation.
A duplication `dup a b = x` compiles into a Constructor node too, but with a different label.
A superposition `{a b}` compiles to a Constructor node too. The difference here comes from context too.
Additionally, nodes have labels. We use the label to store data in the node's memory, which can be used for various purposes.
A duplication `let {a b} = x` compiles into a Duplicator node.
A superposition `{a b}` compiles to a Duplicator node too. The difference here comes from context too.
```
0 - Points to the sup occurrence 0 - Points to the duplicated value

View File

@ -17,7 +17,7 @@ main = (if true 42 37)
// so (true 42 37) will do the same thing.
```
This is how a`Not` function that acts on this encoding can be defined
This is how a `Not` function that acts on this encoding can be defined
```rs
not = λboolean (boolean false true)
main = (not true) // Outputs λtλf f.
@ -120,37 +120,33 @@ Broadly speaking, a good rule of thumb in HVM is **push linear lambdas to the to
## Example
To show the power of fusing, here is a program that self-composes `fusing_not` 2^512 times and prints the result. `2^512` is larger than amount of atoms in the observable universe, and yet HVM is still able to work with it due to its optimal sharing capabilities.
To show the power of fusing, here is a program that self-composes `fusing_not` 2^24 times and prints the result.
Currently hvm is not able to handle operations between church numbers so we explicitly convert the native number to a church number in this example (which is very slow).
This program uses [native numbers, which are described here](native-numbers.md).
```rs
true = λt λf t
false = λt λf f
not = λboolean (boolean false true)
fusing_not = λboolean λt λf (boolean f t)
// Creates a Church numeral out of a native number
to_church n = switch n {
0: λf λx x
_: λf λx (f (to_church n-1 f x))
}
main =
let two = λf λx (f (f x))
let two_pow_512 = ((to_church 512) two) // Composition of church-encoded numbers is equivalent to exponentiation.
// Self-composes `not` 2^512 times and prints the result.
(two_pow_512 fusing_not) // try replacing this by regular not. Will it still work?
((to_church 0xFFFFFF) fusing_not) // try replacing this by regular not. Will it still work?
```
Here is the program's output:
```bash
$ hvml run -s fuse_magic.hvm
λa λb λc (a b c)
RWTS : 15374
- ANNI : 8193
- COMM : 5116
- ERAS : 521
- DREF : 1031
- OPER : 513
TIME : 0.002 s
RPS : 9.537 m
$ bend norm -s fuse_magic.hvm
Result: λa λb λc (a c b)
- ITRS: 285212661
- TIME: 5.67s
- MIPS: 50.28
```
Only 15374 rewrites! Fusing is really powerful.
A lot of rewrites, but most of those are just to create the church number.

View File

@ -1,11 +1,11 @@
use clap::{Args, CommandFactory, Parser, Subcommand};
use hvml::{
use bend::{
check_book, compile_book, desugar_book,
diagnostics::{Diagnostics, DiagnosticsConfig, Severity},
load_file_to_book, run_book,
term::{Book, Name},
CompileOpts, OptLevel, RunOpts,
};
use clap::{Args, CommandFactory, Parser, Subcommand};
use std::path::{Path, PathBuf};
#[derive(Parser, Debug)]
@ -80,8 +80,8 @@ enum Mode {
#[arg(help = "Path to the input file")]
path: PathBuf,
#[arg(value_parser = |arg: &str| hvml::term::parser::TermParser::new(arg).parse_term())]
arguments: Option<Vec<hvml::term::Term>>,
#[arg(value_parser = |arg: &str| bend::term::parser::TermParser::new(arg).parse_term())]
arguments: Option<Vec<bend::term::Term>>,
},
/// Runs the lambda-term level desugaring passes.
Desugar {

View File

@ -1,7 +1,7 @@
pub mod hvmc_to_net;
use crate::term::Name;
pub type HvmlLab = u16;
pub type BendLab = u16;
use NodeKind::*;
#[derive(Debug, Clone)]
@ -41,13 +41,13 @@ pub enum NodeKind {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CtrKind {
Con(Option<HvmlLab>),
Tup(Option<HvmlLab>),
Dup(HvmlLab),
Con(Option<BendLab>),
Tup(Option<BendLab>),
Dup(BendLab),
}
impl CtrKind {
pub fn to_lab(self) -> HvmlLab {
pub fn to_lab(self) -> BendLab {
#[allow(clippy::identity_op)]
match self {
CtrKind::Con(None) => 0,

View File

@ -85,7 +85,7 @@ impl<'a> PyParser<'a> {
} else if self.try_consume("False") {
return Ok(Term::Num { val: 0 });
}
Term::Var { nam: self.parse_hvml_name()? }
Term::Var { nam: self.parse_bend_name()? }
}
};
Ok(res)
@ -95,7 +95,7 @@ impl<'a> PyParser<'a> {
self.consume("[")?;
let head = self.parse_term_py()?;
if self.try_consume_keyword("for") {
let bind = self.parse_hvml_name()?;
let bind = self.parse_bend_name()?;
self.consume("in")?;
let iter = self.parse_term_py()?;
let mut cond = None;
@ -116,7 +116,7 @@ impl<'a> PyParser<'a> {
fn parse_term_py(&mut self) -> Result<Term, String> {
self.skip_trivia();
if self.try_consume_keyword("lambda") {
let names = self.list_like(|p| p.parse_hvml_name(), "", ":", ",", true, 1)?;
let names = self.list_like(|p| p.parse_bend_name(), "", ":", ",", true, 1)?;
let bod = self.parse_term_py()?;
Ok(Term::Lam { names, bod: Box::new(bod) })
} else {
@ -254,7 +254,7 @@ impl<'a> PyParser<'a> {
if self.starts_with("(") {
self.parse_assignment_py(indent)
} else {
let name = self.parse_hvml_name()?;
let name = self.parse_bend_name()?;
if self.skip_starts_with("=") {
// it's actually an assignment
self.consume("=")?;
@ -316,7 +316,7 @@ impl<'a> PyParser<'a> {
fn parse_as_bind(&mut self) -> Result<Option<Name>, String> {
let mut bind = None;
if self.try_consume_keyword("as") {
bind = Some(self.parse_hvml_name()?);
bind = Some(self.parse_bend_name()?);
}
Ok(bind)
}
@ -343,7 +343,7 @@ impl<'a> PyParser<'a> {
if p.try_consume("_") {
Ok(None)
} else {
let nam = p.parse_hvml_name()?;
let nam = p.parse_bend_name()?;
Ok(Some(nam))
}
},
@ -405,7 +405,7 @@ impl<'a> PyParser<'a> {
}
fn parse_fold_py(&mut self, indent: &mut Indent) -> Result<Stmt, String> {
let fun = self.parse_hvml_name()?;
let fun = self.parse_bend_name()?;
let arg = self.parse_term_py()?;
let bind = self.parse_as_bind()?;
self.consume(":")?;
@ -422,7 +422,7 @@ impl<'a> PyParser<'a> {
}
fn parse_do_py(&mut self, indent: &mut Indent) -> Result<Stmt, String> {
let fun = self.parse_hvml_name()?;
let fun = self.parse_bend_name()?;
self.consume(":")?;
let mut block = Vec::new();
@ -458,14 +458,14 @@ impl<'a> PyParser<'a> {
fn parse_assign_pattern_py(&mut self) -> Result<AssignPattern, String> {
if self.skip_starts_with("(") {
let mut binds = self.list_like(|p| p.parse_hvml_name(), "(", ")", ",", true, 1)?;
let mut binds = self.list_like(|p| p.parse_bend_name(), "(", ")", ",", true, 1)?;
if binds.len() == 1 {
Ok(AssignPattern::Var(std::mem::take(&mut binds[0])))
} else {
Ok(AssignPattern::Tup(binds))
}
} else {
self.parse_hvml_name().map(AssignPattern::Var)
self.parse_bend_name().map(AssignPattern::Var)
}
}
@ -481,8 +481,8 @@ impl<'a> PyParser<'a> {
}
fn parse_def_py(&mut self, indent: &mut Indent) -> Result<Definition, String> {
let name = self.parse_hvml_name()?;
let params = self.list_like(|p| p.parse_hvml_name(), "(", ")", ",", true, 0)?;
let name = self.parse_bend_name()?;
let params = self.list_like(|p| p.parse_bend_name(), "(", ")", ",", true, 0)?;
self.consume(":")?;
indent.enter_level();
let body = self.parse_stmt_py(indent)?;
@ -491,7 +491,7 @@ impl<'a> PyParser<'a> {
}
fn parse_enum_py(&mut self, indent: &mut Indent) -> Result<Enum, String> {
let name = self.parse_hvml_name()?;
let name = self.parse_bend_name()?;
let mut variants = Vec::new();
self.consume(":")?;
indent.enter_level();
@ -499,10 +499,10 @@ impl<'a> PyParser<'a> {
if !self.skip_exact_indent(indent, true)? {
break;
}
let name = self.parse_hvml_name()?;
let name = self.parse_bend_name()?;
let mut fields = Vec::new();
if self.skip_starts_with("(") {
fields = self.list_like(|p| p.parse_hvml_name(), "(", ")", ",", true, 0)?;
fields = self.list_like(|p| p.parse_bend_name(), "(", ")", ",", true, 0)?;
}
variants.push((name.clone(), Variant { name, fields }));
}

View File

@ -8,7 +8,7 @@ use crate::{
use highlight_error::highlight_error;
use TSPL::Parser;
// hvml grammar description:
// Bend grammar description:
// <Book> ::= (<Data> | <Rule>)*
// <Data> ::= "data" <Name> "=" ( <Name> | "(" <Name> (<Name>)* ")" )+
// <Rule> ::= ("(" <Name> <Pattern>* ")" | <Name> <Pattern>*) "=" <Term>
@ -95,7 +95,7 @@ impl<'a> TermParser<'a> {
if self.try_consume("(") {
// (name field*)
let name = self.parse_top_level_name()?;
let field_parser = |p: &mut Self| p.labelled(|p| p.parse_hvml_name(), "datatype constructor field");
let field_parser = |p: &mut Self| p.labelled(|p| p.parse_bend_name(), "datatype constructor field");
let fields = self.list_like(field_parser, "", ")", "", false, 0)?;
Ok((name, fields))
} else {
@ -194,7 +194,7 @@ impl<'a> TermParser<'a> {
if self.starts_with("$") {
unexpected_tag(self)?;
self.advance_one();
let name = self.parse_hvml_name()?;
let name = self.parse_bend_name()?;
return Ok(Pattern::Chn(name));
}
@ -286,7 +286,7 @@ impl<'a> TermParser<'a> {
if self.starts_with("$") {
self.consume("$")?;
unexpected_tag(self)?;
let nam = self.parse_hvml_name()?;
let nam = self.parse_bend_name()?;
return Ok(Term::Lnk { nam });
}
@ -376,7 +376,7 @@ impl<'a> TermParser<'a> {
// Use
if self.try_consume_keyword("use") {
unexpected_tag(self)?;
let nam = self.parse_hvml_name()?;
let nam = self.parse_bend_name()?;
self.consume("=")?;
let val = self.parse_term()?;
self.try_consume(";");
@ -421,7 +421,7 @@ impl<'a> TermParser<'a> {
// Var
unexpected_tag(self)?;
let nam = self.labelled(|p| p.parse_hvml_name(), "term")?;
let nam = self.labelled(|p| p.parse_bend_name(), "term")?;
Ok(Term::Var { nam })
})
}
@ -443,7 +443,7 @@ impl<'a> TermParser<'a> {
fn parse_top_level_name(&mut self) -> Result<Name, String> {
let ini_idx = *self.index();
let nam = self.parse_hvml_name()?;
let nam = self.parse_bend_name()?;
let end_idx = *self.index();
if nam.contains("__") {
let ctx = highlight_error(ini_idx, end_idx, self.input());
@ -459,7 +459,7 @@ impl<'a> TermParser<'a> {
if p.try_consume("*") {
Ok(None)
} else {
let nam = p.parse_hvml_name()?;
let nam = p.parse_bend_name()?;
Ok(Some(nam))
}
},
@ -477,9 +477,6 @@ impl<'a> TermParser<'a> {
{
let ctx = highlight_error(index, index + 1, self.input);
return Err(format!("Tagged terms not supported for hvm32.\n{ctx}"));
/* self.advance_one();
let nam = self.labelled(|p| p.parse_hvml_name(), "tag name")?;
Some(Tag::Named(nam)) */
} else {
None
};
@ -496,13 +493,13 @@ impl<'a> TermParser<'a> {
}
fn parse_match_arg(&mut self) -> Result<(Name, Term, Vec<Name>), String> {
let bnd = self.parse_hvml_name()?;
let bnd = self.parse_bend_name()?;
let arg = if self.try_consume("=") { self.parse_term()? } else { Term::Var { nam: bnd.clone() } };
let with = if self.try_consume_keyword("with") {
let mut with = vec![self.parse_hvml_name()?];
let mut with = vec![self.parse_bend_name()?];
while !self.skip_starts_with("{") {
self.try_consume(",");
with.push(self.parse_hvml_name()?);
with.push(self.parse_bend_name()?);
}
with
} else {
@ -755,7 +752,7 @@ pub trait ParserCommons<'a>: Parser<'a> {
}
}
fn parse_hvml_name(&mut self) -> Result<Name, String> {
fn parse_bend_name(&mut self) -> Result<Name, String> {
let nam = self.parse_name()?;
Ok(Name::new(nam))
}

View File

@ -10,7 +10,7 @@ impl Ctx<'_> {
/// ```hvm
/// main x1 x2 x3 = (MainBody x1 x2 x3)
/// ```
/// Calling with `hvml run <file> arg1 arg2 arg3`, it becomes:
/// Calling with `bend run <file> arg1 arg2 arg3`, it becomes:
/// ```hvm
/// main = (λx1 λx2 λx3 (MainBody x1 x2 x3) arg1 arg2 arg3)
/// ```

View File

@ -7,7 +7,7 @@ impl Book {
/// Inline copies of the declared bind in the `use` expression.
///
/// Example:
/// ```hvml
/// ```bend
/// use id = λx x
/// (id id id)
///

View File

@ -1,4 +1,4 @@
use hvml::{
use bend::{
compile_book, desugar_book,
diagnostics::{Diagnostics, DiagnosticsConfig, Severity},
net::hvmc_to_net::hvmc_to_net,
@ -107,7 +107,7 @@ fn compile_term() {
term.make_var_names_unique();
term.linearize_vars();
let net = hvml::term::term_to_net(&term, &mut Default::default()).map_err(|e| e.to_string())?;
let net = bend::term::term_to_net(&term, &mut Default::default()).map_err(|e| e.to_string())?;
Ok(format!("{}", net))
})
@ -369,7 +369,7 @@ fn cli() {
let args = args_buf.lines();
let output =
std::process::Command::new(env!("CARGO_BIN_EXE_hvml")).args(args).output().expect("Run command");
std::process::Command::new(env!("CARGO_BIN_EXE_bend")).args(args).output().expect("Run command");
let res =
format!("{}{}", String::from_utf8_lossy(&output.stderr), String::from_utf8_lossy(&output.stdout));
Ok(res)

View File

@ -6,6 +6,6 @@ error: unexpected argument '-d' found
tip: to pass '-d' as a value, use '-- -d'
Usage: hvml run [OPTIONS] <PATH> [ARGUMENTS]...
Usage: bend run [OPTIONS] <PATH> [ARGUMENTS]...
For more information, try '--help'.

View File

@ -6,6 +6,6 @@ error: unexpected argument '-d' found
tip: to pass '-d' as a value, use '-- -d'
Usage: hvml run [OPTIONS] <PATH> [ARGUMENTS]...
Usage: bend run [OPTIONS] <PATH> [ARGUMENTS]...
For more information, try '--help'.