diff --git a/README.md b/README.md index f3973d0..72ce205 100644 --- a/README.md +++ b/README.md @@ -9,49 +9,85 @@ Read the Docs:
[FFI](https://github.com/vkobinski/benda-main/tree/master/docs/FFI.md) -This is in conceptual stage. +This is in MVP stage. ## Example ```py -from dataclasses import dataclass -from benda import bjit, u24 - -@dataclass -class Leaf: - value: u24 # native HVM machine integer - -@dataclass -class Node: - left: 'Tree' - right: 'Tree' - -Tree = Node | Leaf - -# The `bjit` decorator will introspect and translate the function to HVM/Bend -# code, replacing it with a wrapper that converts the Python-level types of the -# inputs and result value, Numba-style. - -@bjit -def sum_tree(tree: Tree) -> u24: - match tree: - case Leaf(value=value): - return value - case Node(left=left, right=right): - return sum_tree(left) + sum_tree(right) - case _: - raise TypeError("Invalid type for tree") - -# Alternatively, you can opt to use Python big integers and other primitives, -# they will be translated to the equivalent representations automatically. - -@dataclass -class Leaf2: - value: int +import benda +import random + +book = benda.load_book(""" +(Sort List/Nil) = List/Nil +(Sort(List/Cons head tail)) = +((Part head tail) λmin λmax + let lft=(Sort min) + let rgt=(Sort max) + (Concat lft(List/Cons head rgt))) + +# Partitions a list in two halves, less-than-p and greater-than-p +(Part p List/Nil) = λt(t List/Nil List/Nil) +(Part p(List/Cons head tail)) = (Push(> head p) head(Part p tail)) + +# Pushes a value to the first or second list of a pair +(Push 0 x pair) = (pair λmin λmax λp(p(List/Cons x min) max)) +(Push _ x pair) = (pair λmin λmax λp(p min(List/Cons x max))) + +(Concat List/Nil tail) = tail +(Concat(List/Cons head tail) xs2) = +(List/Cons head(Concat tail xs2)) +""") + +List = book.adts.List + +def gen_list(n: int, max_value: int = 0xffffff) -> list[int]: + result: list[int] = [] + for _ in range(n): + result.append(random.randint(0, max_value)) + return result + + +def to_cons_list(xs: list[int]): + result = List.Nil() + + hi = len(xs) + if hi == 0: + return result + + while hi > 0: + hi -= 1 + result = List.Cons(xs[hi], result) + + return result + +def print_cons_list(list): + while True: + match list: + case List.Cons.type(value, tail): + print(value, end=", ") + list = tail + case List.Nil.type(): + break + + +data = gen_list(5, 1000) +cons_list = to_cons_list(data) +sorted_list = book.defs.Sort(cons_list) +sorted_list = sorted_list.to_adt(book.adts.List) +print_cons_list(sorted_list) ``` ## Development +Dependencies: + +- Python 3.11+ +- Rust +- C compiler +- maturin + +### Getting dependencies with Nix (optional) + - Install Nix with [Determinate Nix Installer] ```sh @@ -61,8 +97,20 @@ class Leaf2: - You can run `nix develop` to enter a shell with the dependencies installed. -- You can use [`direnv`][direnv] to automatically load the environment when you - enter the project directory. +### Building + +- Create and activate a Python virtual environment. + - e.g. with + ``` + python -m venv .venv + source .venv/bin/activate + ``` + +- Run `make` to build the project and install the `benda` package in the virtual + environment. + + [Determinate Nix Installer]: https://install.determinate.systems [direnv]: https://direnv.net diff --git a/crates/benda/src/benda_ffi/mod.rs b/crates/benda/src/benda_ffi/mod.rs index afbe41d..28c39d0 100644 --- a/crates/benda/src/benda_ffi/mod.rs +++ b/crates/benda/src/benda_ffi/mod.rs @@ -2,6 +2,10 @@ use bend::diagnostics::{Diagnostics, DiagnosticsConfig}; use bend::fun::{Book, Term}; use bend::{CompileOpts, RunOpts}; +/** + * TODO: move to another module + * TODO: docstring + */ pub fn run(book: &Book) -> Option<(Term, String, Diagnostics)> { let run_opts = RunOpts::default(); let compile_opts = CompileOpts::default().set_all(); diff --git a/crates/benda/src/lib.rs b/crates/benda/src/lib.rs index 8d518bd..27d5bee 100644 --- a/crates/benda/src/lib.rs +++ b/crates/benda/src/lib.rs @@ -1,3 +1,5 @@ +use std::fs::{remove_dir, File}; +use std::io::Write; use std::path::Path; use num_traits::ToPrimitive; @@ -18,21 +20,30 @@ fn switch() -> PyResult { Ok("Ok".to_string()) } +#[pyfunction] +fn load_book(py: Python, code: Py) -> PyResult> { + let mut tmp_file = File::create_new("./tmp/bend_book.tmp") + .expect("Could not create temporary file."); + + let _ = tmp_file.write_all(code.to_string().as_bytes()); + let _ = tmp_file.flush(); + + let path = Path::new("./tmp/bend_book.tmp"); + let bend_book = bend::load_file_to_book(path); + + let _ = std::fs::remove_file(path); + + let book = Book::new(&mut bend_book.unwrap()); + + Ok(Py::new(py, book).unwrap()) +} + #[pyfunction] fn load_book_from_file(py: Python, path: Py) -> PyResult> { let binding = path.to_string(); let new_path = Path::new(&binding); let bend_book = bend::load_file_to_book(new_path); - //let code = std::fs::read_to_string(new_path) - // .map_err(|e| e.to_string()) - // .unwrap(); - //let bend_book = bend::fun::load_book::do_parse_book( - // &code, - // new_path, - // BendBook::default(), - //); - let book = Book::new(&mut bend_book.unwrap()); Ok(Py::new(py, book).unwrap()) @@ -138,6 +149,7 @@ impl PyBjit { fn benda(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(switch, m)?)?; m.add_function(wrap_pyfunction!(load_book_from_file, m)?)?; + m.add_function(wrap_pyfunction!(load_book, m)?)?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/crates/benda/src/parser/builtins.rs b/crates/benda/src/parser/builtins.rs deleted file mode 100644 index e0d80ea..0000000 --- a/crates/benda/src/parser/builtins.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub enum BuiltIn { - Switch, -} \ No newline at end of file diff --git a/crates/benda/src/types/book.rs b/crates/benda/src/types/book.rs index 23d05f4..2ba38b4 100644 --- a/crates/benda/src/types/book.rs +++ b/crates/benda/src/types/book.rs @@ -71,7 +71,7 @@ macro_rules! generate_structs { #[pyclass(name = $name)] #[derive(Clone, Debug)] pub struct $iden { - entire_name: String, + full_name: String, name: String, fields: IndexMap>>, } @@ -105,10 +105,7 @@ macro_rules! generate_structs { fn __str__(&self) -> String { let mut out = String::new(); - out.push_str( - format!("Bend ADT: {}", self.entire_name).as_str(), - ); - + out.push_str(format!("", self.full_name).as_str()); out } @@ -136,19 +133,29 @@ macro_rules! generate_structs { } } + #[getter] + fn r#type(&self) -> PyResult { + Python::with_gil(|py| { + let ctr_type = $iden::type_object_bound(py).to_object(py); + Ok(ctr_type) + }) + } + fn __getattr__(&self, object: Bound) -> PyResult { let field = object.to_string(); let py = object.py(); - if field == "type" { + if field == "__variant" { return Ok( - PyString::new_bound(py, &self.entire_name).into_py(py) + PyString::new_bound(py, &self.full_name).into_py(py) ); } - if &object.to_string() == "name" { - return Ok(PyString::new_bound(py, &self.name).into()); + if field == "__ctr_type__" { + return Ok( + PyString::new_bound(py, &self.full_name).into_py(py) + ); } if let Ok(val) = object.to_string().parse::() { @@ -173,6 +180,9 @@ generate_structs!("Ctr2", Ctr2); generate_structs!("Ctr3", Ctr3); generate_structs!("Ctr4", Ctr4); generate_structs!("Ctr5", Ctr5); +generate_structs!("Ctr6", Ctr6); +generate_structs!("Ctr7", Ctr7); +generate_structs!("Ctr8", Ctr8); #[pyclass(name = "Ctrs")] #[derive(Clone, Debug, Default)] @@ -183,6 +193,9 @@ pub struct Ctrs { pub third: Option, pub fourth: Option, pub fifth: Option, + pub sixth: Option, + pub seventh: Option, + pub eighth: Option, } impl Ctrs { @@ -208,13 +221,10 @@ impl Ctrs { #[pymethods] impl Ctrs { - fn __getattr__(&self, object: Bound) -> PyResult { - let py = object.py(); - - if object.to_string().starts_with('t') { - let b_name = object.to_string(); - let name = b_name.strip_prefix('t').unwrap(); + fn __getattr__(&self, name: Bound) -> PyResult { + let py = name.py(); + if let Some(name) = name.to_string().strip_suffix("_t") { let index = self.fields.get_index_of(name).unwrap(); let res = match index { @@ -233,10 +243,10 @@ impl Ctrs { return Ok(res.to_object(py)); } - if let Some(val) = self.fields.get(&object.to_string()) { + if let Some(val) = self.fields.get(&name.to_string()) { Ok(val.clone()) } else { - new_err(format!("Could not find attr {}", object)) + new_err(format!("Could not find attr {}", name)) } } } @@ -422,6 +432,9 @@ impl Book { let mut third: Option = None; let mut fourth: Option = None; let mut fifth: Option = None; + let mut sixth: Option = None; + let mut seventh: Option = None; + let mut eighth: Option = None; for (index, (ctr_name, ctr_fields)) in bend_adt.ctrs.iter().enumerate() @@ -432,7 +445,7 @@ impl Book { 0 => { let mut ct = Ctr1 { name: new_name.clone(), - entire_name: ctr_name.to_string(), + full_name: ctr_name.to_string(), fields: IndexMap::new(), }; for c in ctr_fields { @@ -446,7 +459,7 @@ impl Book { 1 => { let mut ct = Ctr2 { name: new_name.clone(), - entire_name: ctr_name.to_string(), + full_name: ctr_name.to_string(), fields: IndexMap::new(), }; for c in ctr_fields { @@ -460,7 +473,7 @@ impl Book { 2 => { let mut ct = Ctr3 { name: new_name.clone(), - entire_name: ctr_name.to_string(), + full_name: ctr_name.to_string(), fields: IndexMap::new(), }; for c in ctr_fields { @@ -474,7 +487,7 @@ impl Book { 3 => { let mut ct = Ctr4 { name: new_name.clone(), - entire_name: ctr_name.to_string(), + full_name: ctr_name.to_string(), fields: IndexMap::new(), }; for c in ctr_fields { @@ -489,7 +502,7 @@ impl Book { 4 => { let mut ct = Ctr5 { name: new_name.clone(), - entire_name: ctr_name.to_string(), + full_name: ctr_name.to_string(), fields: IndexMap::new(), }; for c in ctr_fields { @@ -501,6 +514,49 @@ impl Book { .insert(new_name, ct.into_py(py).as_any().clone()); } + 5 => { + let mut ct = Ctr6 { + name: new_name.clone(), + full_name: ctr_name.to_string(), + fields: IndexMap::new(), + }; + for c in ctr_fields { + ct.fields.insert(c.nam.to_string(), None); + } + sixth = Some(ct.clone()); + all_ctrs + .fields + .insert(new_name, ct.into_py(py).as_any().clone()); + } + 6 => { + let mut ct = Ctr7 { + name: new_name.clone(), + full_name: ctr_name.to_string(), + fields: IndexMap::new(), + }; + for c in ctr_fields { + ct.fields.insert(c.nam.to_string(), None); + } + seventh = Some(ct.clone()); + all_ctrs + .fields + .insert(new_name, ct.into_py(py).as_any().clone()); + } + + 7 => { + let mut ct = Ctr8 { + name: new_name.clone(), + full_name: ctr_name.to_string(), + fields: IndexMap::new(), + }; + for c in ctr_fields { + ct.fields.insert(c.nam.to_string(), None); + } + eighth = Some(ct.clone()); + all_ctrs + .fields + .insert(new_name, ct.into_py(py).as_any().clone()); + } _ => panic!("Type must have up to 5 Ctrs"), }); } @@ -510,6 +566,9 @@ impl Book { all_ctrs.third = third; all_ctrs.fourth = fourth; all_ctrs.fifth = fifth; + all_ctrs.sixth = sixth; + all_ctrs.seventh = seventh; + all_ctrs.eighth = eighth; adts.adts.insert(adt_name.to_string(), all_ctrs); } @@ -541,22 +600,25 @@ impl Book { #[pymethods] impl Book { - fn __getattr__(&self, object: Bound) -> PyResult { - let binding = object.to_string(); - let field = binding.as_str(); - let py = object.py(); + #[getter] + fn adts(&self) -> PyResult { + Python::with_gil(|py| { + let adt = &self.adts; + Ok(adt.clone().into_py(py)) + }) + } - match field { - "adts" => { - let adt = &self.adts; - Ok(adt.clone().into_py(py)) - } - "defs" => { - let def = &self.defs; - Ok(def.clone().into_py(py)) - } + #[getter] + fn defs(&self) -> PyResult { + Python::with_gil(|py| { + let defs = &self.defs; + Ok(defs.clone().into_py(py)) + }) + } - _ => new_err(format!("Could not find attribute {}", object)), - } + fn __getattr__(&self, attr_name: Bound) -> PyResult { + let attr_name = attr_name.to_string(); + + new_err(format!("Could not find attribute {}", attr_name)) } } diff --git a/crates/benda/src/types/mod.rs b/crates/benda/src/types/mod.rs index eeb4be0..e38dce8 100644 --- a/crates/benda/src/types/mod.rs +++ b/crates/benda/src/types/mod.rs @@ -18,6 +18,9 @@ pub mod tree; pub mod u24; pub mod user_adt; +/** + * TODO: document + */ pub trait BendType { fn to_bend(&self) -> ToBendResult; } diff --git a/crates/benda/src/types/user_adt.rs b/crates/benda/src/types/user_adt.rs index 5a4fd14..5243537 100644 --- a/crates/benda/src/types/user_adt.rs +++ b/crates/benda/src/types/user_adt.rs @@ -93,6 +93,9 @@ pub fn from_term_into_adt(term: &BTerm, def_adts: &Ctrs) -> Option { 2 => Some(Box::new(def_adts.third.clone().unwrap())), 3 => Some(Box::new(def_adts.fourth.clone().unwrap())), 4 => Some(Box::new(def_adts.fifth.clone().unwrap())), + 5 => Some(Box::new(def_adts.sixth.clone().unwrap())), + 6 => Some(Box::new(def_adts.seventh.clone().unwrap())), + 7 => Some(Box::new(def_adts.eighth.clone().unwrap())), _ => panic!("ADT has more than 5 Ctrs"), }; @@ -143,6 +146,9 @@ pub fn from_term_into_adt(term: &BTerm, def_adts: &Ctrs) -> Option { } } +/** + * TODO: document + */ #[derive(Debug, Clone)] pub struct UserAdt<'py> { adt: BAdt, @@ -162,7 +168,7 @@ impl<'py> UserAdt<'py> { // return None; //} - if let Ok(binding) = data.getattr("type") { + if let Ok(binding) = data.getattr("__ctr_type__") { for (nam, _ctr) in &book.ctrs { let new_nam = nam.to_string(); let two_names = new_nam.split_once('/').unwrap(); diff --git a/examples/future/old_readme.py b/examples/future/old_readme.py new file mode 100644 index 0000000..d5db25e --- /dev/null +++ b/examples/future/old_readme.py @@ -0,0 +1,34 @@ +from dataclasses import dataclass +from benda import bjit, u24 + +@dataclass +class Leaf: + value: u24 # native HVM machine integer + +@dataclass +class Node: + left: 'Tree' + right: 'Tree' + +Tree = Node | Leaf + +# The `bjit` decorator will introspect and translate the function to HVM/Bend +# code, replacing it with a wrapper that converts the Python-level types of the +# inputs and result value, Numba-style. + +@bjit +def sum_tree(tree: Tree) -> u24: + match tree: + case Leaf(value=value): + return value + case Node(left=left, right=right): + return sum_tree(left) + sum_tree(right) + case _: + raise TypeError("Invalid type for tree") + +# Alternatively, you can opt to use Python big integers and other primitives, +# they will be translated to the equivalent representations automatically. + +@dataclass +class Leaf2: + value: int