Skip to content

Commit

Permalink
a uniplate POC
Browse files Browse the repository at this point in the history
  • Loading branch information
niklasdewally committed Feb 6, 2024
1 parent fd1711d commit 03d77cc
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 1 deletion.
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ default-members = [
"crates/conjure_rules",
"solvers/kissat",
"solvers/minion",
"crates/uniplate",
]

members = [
Expand All @@ -15,6 +16,7 @@ members = [
"crates/doc_solver_support",
"crates/conjure_rules_proc_macro",
"crates/conjure_rules",
"crates/uniplate",
"solvers/kissat",
"solvers/minion",
"solvers/chuffed",
Expand Down
13 changes: 13 additions & 0 deletions crates/uniplate/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "uniplate"
version = "0.1.0"
edition = "2021"
description="Boilerplate-free generic operations on data, Haskell style."
license = "MPL-2.0"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

[lints]
workspace = true
109 changes: 109 additions & 0 deletions crates/uniplate/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//! A port of Haskell's [Uniplate](https://hackage.haskell.org/package/uniplate) in Rust.
//!
//!
//! # Examples
//!
//! ## A Calculator Input Language
//!
//! Consider the AST of a calculator input language:
//!
//! ```
//! pub enum AST {
//! Int(i32),
//! Add(Box<AST>,Box<AST>),
//! Sub(Box<AST>,Box<AST>),
//! Div(Box<AST>,Box<AST>),
//! Mul(Box<AST>,Box<AST>)
//! }
//!```
//!
//! Using uniplate, one can implement a single function for this AST that can be used in a whole
//! range of traversals.
//!
//! While this does not seem helpful in this toy example, the benefits amplify when the number of
//! enum variants increase, and the different types contained in their fields increase.
//!
//!
//! The below example implements [`Uniplate`](uniplate::Uniplate) for this language AST, and uses uniplate methods to
//! evaluate the encoded equation.
//!
//!```
//! use uniplate::uniplate::Uniplate;
//!
//! #[derive(Clone,Eq,PartialEq,Debug)]
//! pub enum AST {
//! Int(i32),
//! Add(Box<AST>,Box<AST>),
//! Sub(Box<AST>,Box<AST>),
//! Div(Box<AST>,Box<AST>),
//! Mul(Box<AST>,Box<AST>)
//! }
//!
//! // In the future would be automatically derived.
//! impl Uniplate for AST {
//! fn uniplate(&self) -> (Vec<AST>, Box<dyn Fn(Vec<AST>) -> AST +'_>) {
//! let context: Box<dyn Fn(Vec<AST>) -> AST> = match self {
//! AST::Int(i) => Box::new(|_| AST::Int(*i)),
//! AST::Add(_, _) => Box::new(|exprs: Vec<AST>| AST::Add(Box::new(exprs[0].clone()),Box::new(exprs[1].clone()))),
//! AST::Sub(_, _) => Box::new(|exprs: Vec<AST>| AST::Sub(Box::new(exprs[0].clone()),Box::new(exprs[1].clone()))),
//! AST::Div(_, _) => Box::new(|exprs: Vec<AST>| AST::Div(Box::new(exprs[0].clone()),Box::new(exprs[1].clone()))),
//! AST::Mul(_, _) => Box::new(|exprs: Vec<AST>| AST::Mul(Box::new(exprs[0].clone()),Box::new(exprs[1].clone())))
//! };
//!
//! let children: Vec<AST> = match self {
//! AST::Add(a,b) => vec![*a.clone(),*b.clone()],
//! AST::Sub(a,b) => vec![*a.clone(),*b.clone()],
//! AST::Div(a,b) => vec![*a.clone(),*b.clone()],
//! AST::Mul(a,b) => vec![*a.clone(),*b.clone()],
//! _ => vec![]
//! };
//!
//! (children,context)
//! }
//! }
//!
//! pub fn my_rule(e: AST) -> AST{
//! match e {
//! AST::Int(a) => AST::Int(a),
//! AST::Add(a,b) => {match (&*a,&*b) { (AST::Int(a), AST::Int(b)) => AST::Int(a+b), _ => AST::Add(a,b) }},
//! AST::Sub(a,b) => {match (&*a,&*b) { (AST::Int(a), AST::Int(b)) => AST::Int(a-b), _ => AST::Sub(a,b) }},
//! AST::Mul(a,b) => {match (&*a,&*b) { (AST::Int(a), AST::Int(b)) => AST::Int(a*b), _ => AST::Mul(a,b) }},
//! AST::Div(a,b) => {match (&*a,&*b) { (AST::Int(a), AST::Int(b)) => AST::Int(a/b), _ => AST::Div(a,b) }}
//! }
//! }
//! pub fn main() {
//! let mut ast = AST::Add(
//! Box::new(AST::Int(1)),
//! Box::new(AST::Mul(
//! Box::new(AST::Int(2)),
//! Box::new(AST::Div(
//! Box::new(AST::Add(Box::new(AST::Int(1)),Box::new(AST::Int(2)))),
//! Box::new(AST::Int(3))
//! )))));
//!
//! ast = ast.transform(my_rule);
//! println!("{:?}",ast);
//! assert_eq!(ast,AST::Int(3));
//! }
//! ```
//!
//! ....MORE DOCS TO COME....
//!
//! # Acknowledgements / Related Work
//!
//! **This crate implements programming constructs from the following Haskell libraries and
//! papers:**
//!
//! * [Uniplate](https://hackage.haskell.org/package/uniplate).
//!
//! * Neil Mitchell and Colin Runciman. 2007. Uniform boilerplate and list processing. In
//! Proceedings of the ACM SIGPLAN workshop on Haskell workshop (Haskell '07). Association for
//! Computing Machinery, New York, NY, USA, 49–60. <https://doi.org/10.1145/1291201.1291208>
//! [(free copy)](https://www.cs.york.ac.uk/plasma/publications/pdf/MitchellRuncimanHW07.pdf)
//!
//! * Huet G. The Zipper. Journal of Functional Programming. 1997;7(5):549–54. <https://doi.org/10.1017/S0956796897002864>
//! [(free copy)](https://www.cambridge.org/core/services/aop-cambridge-core/content/view/0C058890B8A9B588F26E6D68CF0CE204/S0956796897002864a.pdf/zipper.pdf)
//!
pub mod uniplate;
71 changes: 71 additions & 0 deletions crates/uniplate/src/uniplate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
pub trait Uniplate
where
Self: Sized + Clone + Eq,
{
fn uniplate(&self) -> (Vec<Self>, Box<dyn Fn(Vec<Self>) -> Self + '_>);

/// Get the DIRECT children of a node.
fn children(self) -> Vec<Self> {
self.uniplate().0
}

/// Get all children of a node, including itself and all children.
fn universe(self) -> Vec<Self> {
let mut results = vec![self.clone()];
for child in self.children() {
results.append(&mut child.universe());
}
results
}

/// Apply the given rule to all nodes bottom up.
fn transform(self, f: fn(Self) -> Self) -> Self {
let (children, context) = self.uniplate();
f(context(
children.into_iter().map(|a| a.transform(f)).collect(),
))
}

fn rewrite(self, f: fn(Self) -> Option<Self>) -> Self {
todo!()
}

/// Perform a transformation on all the immediate children, then combine them back.
/// This operation allows additional information to be passed downwards, and can be used to provide a top-down transformation.
fn descend(self, f: fn(Self) -> Self) -> Self {
let (children, context) = self.uniplate();
let children: Vec<Self> = children.into_iter().map(f).collect();

context(children)
}

fn zipper(self) -> Zipper<Self> {
todo!()
}
}

pub struct Zipper<T>
where
T: Uniplate,
{
hole: T,
// TODO
}

impl<T: Uniplate> Zipper<T> {
fn left(self) -> Option<Zipper<T>> {
todo!();
}

fn right(self) -> Option<Zipper<T>> {
todo!();
}

fn up(self) -> Option<Zipper<T>> {
todo!();
}

fn down(self) -> Option<Zipper<T>> {
todo!();
}
}
2 changes: 1 addition & 1 deletion tools/gen_docs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ TARGET_DIR=$(cargo metadata 2> /dev/null | jq -r .target_directory 2>/dev/null)
cd "$PROJECT_ROOT"
rm -rf "$TARGET_DIR/doc"

RUSTDOCFLAGS="-Zunstable-options --markdown-no-toc --index-page ${PROJECT_ROOT}/doc/index.md" cargo +nightly doc --no-deps -p conjure_oxide -p conjure_core -p minion_rs -p chuffed_rs -p conjure_rules
RUSTDOCFLAGS="-Zunstable-options --markdown-no-toc --index-page ${PROJECT_ROOT}/doc/index.md" cargo +nightly doc --no-deps -p conjure_oxide -p conjure_core -p minion_rs -p chuffed_rs -p conjure_rules -p uniplate

0 comments on commit 03d77cc

Please sign in to comment.