Skip to content

Commit

Permalink
Merge pull request #466 from lixitrixi/selection-strategies
Browse files Browse the repository at this point in the history
Additional tree-morph selection strategies
  • Loading branch information
ozgurakgun authored Jan 10, 2025
2 parents a446b2e + b30a1a9 commit 3f12c25
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 10 deletions.
8 changes: 8 additions & 0 deletions Cargo.lock

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

4 changes: 3 additions & 1 deletion crates/tree_morph/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ version = "0.1.0"
edition = "2021"

[dependencies]
uniplate = { version = "0.1.5" }
multipeek = "0.1.2"
rand = "0.8.5"
uniplate = { version = "0.1.0" }


[lints]
Expand Down
4 changes: 2 additions & 2 deletions crates/tree_morph/src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::collections::VecDeque;
use uniplate::Uniplate;

pub enum Command<T, M>
enum Command<T, M>
where
T: Uniplate,
{
Expand All @@ -21,7 +21,7 @@ impl<T, M> Commands<T, M>
where
T: Uniplate,
{
pub fn new() -> Self {
pub(crate) fn new() -> Self {
Self {
commands: VecDeque::new(),
}
Expand Down
141 changes: 138 additions & 3 deletions crates/tree_morph/src/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,29 @@
use uniplate::Uniplate;
use std::{fmt::Display, io::Write, sync::Arc};

use crate::{Reduction, Rule};
use multipeek::multipeek;
use uniplate::Uniplate;

/// Returns the first result if the iterator has only one, otherwise calls `select`.
pub(crate) fn one_or_select<T, M, R>(
select: impl Fn(&T, &mut dyn Iterator<Item = (&R, Reduction<T, M>)>) -> Option<Reduction<T, M>>,
t: &T,
rs: &mut dyn Iterator<Item = (&R, Reduction<T, M>)>,
) -> Option<Reduction<T, M>>
where
T: Uniplate,
R: Rule<T, M>,
{
let mut rs = multipeek(rs);
if rs.peek_nth(1).is_none() {
return rs.next().map(|(_, r)| r);
}
select(t, &mut rs)
}

/// Returns the first available `Reduction` if there is one, otherwise returns `None`.
///
/// This is a good default selection strategy, especially when you expect only one possible result.
pub fn select_first<T, M, R>(
_: &T,
rs: &mut dyn Iterator<Item = (&R, Reduction<T, M>)>,
Expand All @@ -13,5 +35,118 @@ where
rs.next().map(|(_, r)| r)
}

// TODO: Add more selection strategies (e.g. random, smallest subtree, ask the user for input, etc.)
// The engine will also have to use a new function which only calls a selector function if there is >1 result
/// Select the first result or panic if there is more than one.
///
/// This is useful when you expect exactly one rule to be applicable in all cases.
pub fn select_first_or_panic<T, M, R>(
t: &T,
rs: &mut dyn Iterator<Item = (&R, Reduction<T, M>)>,
) -> Option<Reduction<T, M>>
where
T: Uniplate + Display,
R: Rule<T, M>,
{
let mut rs = multipeek(rs);
if rs.peek_nth(1).is_some() {
// TODO (Felix) Log list of rules
panic!("Multiple rules applicable to expression \"{}\"", t);
}
rs.next().map(|(_, r)| r)
}

macro_rules! select_prompt {
() => {
"--- Current Expression ---
{}
--- Rules ---
{}
---
q No change
<n> Apply rule n
:"
};
}

pub fn select_user_input<T, M, R>(
t: &T,
rs: &mut dyn Iterator<Item = (&R, Reduction<T, M>)>,
) -> Option<Reduction<T, M>>
where
T: Uniplate + Display,
R: Rule<T, M> + Display,
{
let mut choices: Vec<_> = rs.collect();

let rules = choices
.iter()
.enumerate()
.map(|(i, (r, Reduction { new_tree, .. }))| {
format!(
"{}. {}
~> {}",
i + 1,
r,
new_tree
)
})
.collect::<Vec<_>>()
.join("\n\n");

loop {
print!(select_prompt!(), t, rules);
std::io::stdout().flush().unwrap(); // Print the : on same line

let mut line = String::new();
std::io::stdin().read_line(&mut line).unwrap();

match line.trim() {
"q" => return None,
n => {
if let Ok(n) = n.parse::<usize>() {
if n > 0 && n <= choices.len() {
let ret = choices.swap_remove(n - 1).1;
return Some(ret);
}
}
}
}
}
}

/// Selects a random `Reduction` from the iterator.
pub fn select_random<T, M, R>(
_: &T,
rs: &mut dyn Iterator<Item = (&R, Reduction<T, M>)>,
) -> Option<Reduction<T, M>>
where
T: Uniplate,
R: Rule<T, M>,
{
use rand::seq::IteratorRandom;
let mut rng = rand::thread_rng();
rs.choose(&mut rng).map(|(_, r)| r)
}

/// Selects the `Reduction` which results in the smallest subtree.
///
/// Subtree size is determined by maximum depth.
/// Among trees with the same depth, the first in the iterator order is selected.
pub fn select_smallest_subtree<T, M, R>(
_: &T,
rs: &mut dyn Iterator<Item = (&R, Reduction<T, M>)>,
) -> Option<Reduction<T, M>>
where
T: Uniplate,
R: Rule<T, M>,
{
rs.min_by_key(|(_, r)| {
r.new_tree.cata(Arc::new(|_, cs: Vec<i32>| {
// Max subtree height + 1
cs.iter().max().unwrap_or(&0) + 1
}))
})
.map(|(_, r)| r)
}
5 changes: 3 additions & 2 deletions crates/tree_morph/src/reduce.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Commands, Reduction, Rule};
use crate::{helpers::one_or_select, Commands, Reduction, Rule};
use uniplate::Uniplate;

// TODO: (Felix) dirty/clean optimisation: replace tree with a custom tree structure,
Expand Down Expand Up @@ -96,7 +96,8 @@ where
{
reduce(
|commands, subtree, meta| {
let selection = select(
let selection = one_or_select(
&select,
subtree,
&mut rules.iter().filter_map(|rule| {
Reduction::apply_transform(|c, t, m| rule.apply(c, t, m), subtree, meta)
Expand Down
4 changes: 2 additions & 2 deletions crates/tree_morph/tests/depend_meta.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Here we test an interesting side-effect case, with rules return a reduction based on the metadata.
//! Here we test an interesting side-effect case, with rules which return a reduction based on a metadata field.
//! These rules will not be run a second time if no other rule applies to the same node, which might be unexpected.
use tree_morph::*;
Expand All @@ -16,7 +16,7 @@ fn transform(cmd: &mut Commands<Expr, bool>, expr: &Expr, meta: &bool) -> Option
if *meta {
return Some(Expr::Two);
} else {
cmd.mut_meta(|m| *m = true); // The next application of this rule should
cmd.mut_meta(|m| *m = true); // The next application of this rule will apply
}
}
None
Expand Down

0 comments on commit 3f12c25

Please sign in to comment.