nickel_lang_core/
traverse.rs

1//! Traversal of trees of objects.
2
3use crate::bytecode::ast::alloc::{Allocable, AstAlloc};
4
5#[derive(Copy, Clone)]
6pub enum TraverseOrder {
7    TopDown,
8    BottomUp,
9}
10
11/// Flow control for tree traverals.
12pub enum TraverseControl<S, U> {
13    /// Normal control flow: continue recursing into the children.
14    ///
15    /// Pass the state &S to all children.
16    ContinueWithScope(S),
17    /// Normal control flow: continue recursing into the children.
18    ///
19    /// The state that was passed to the parent will be re-used for the children.
20    Continue,
21
22    /// Skip this branch of the tree.
23    SkipBranch,
24
25    /// Finish traversing immediately (and return a value).
26    Return(U),
27}
28
29impl<S, U> From<Option<U>> for TraverseControl<S, U> {
30    fn from(value: Option<U>) -> Self {
31        match value {
32            Some(u) => TraverseControl::Return(u),
33            None => TraverseControl::Continue,
34        }
35    }
36}
37
38pub trait Traverse<T>: Sized {
39    /// Apply a transformation on a object containing syntactic elements of type `T` (terms, types,
40    /// etc.) by mapping a faillible function `f` on each such node as prescribed by the order.
41    ///
42    /// `f` may return a generic error `E` and use the state `S` which is passed around.
43    fn traverse<F, E>(self, f: &mut F, order: TraverseOrder) -> Result<Self, E>
44    where
45        F: FnMut(T) -> Result<T, E>;
46
47    /// Recurse through the tree of objects top-down (a.k.a. pre-order), applying `f` to
48    /// each object.
49    ///
50    /// Through its return value, `f` can short-circuit one branch of the traversal or
51    /// the entire traversal.
52    ///
53    /// This traversal can make use of "scoped" state. The `scope` argument is passed to
54    /// each callback, and the callback can optionally override that scope just for its
55    /// own subtree in the traversal. For example, when traversing a tree of terms you can
56    /// maintain an environment. Most of the time the environment should get passed around
57    /// unchanged, but a let binder should override the environment of its subtree. It
58    /// does this by returning a `TraverseControl::ContinueWithScope` that contains the
59    /// new environment.
60    fn traverse_ref<S, U>(
61        &self,
62        f: &mut dyn FnMut(&T, &S) -> TraverseControl<S, U>,
63        scope: &S,
64    ) -> Option<U>;
65
66    fn find_map<S>(&self, mut pred: impl FnMut(&T) -> Option<S>) -> Option<S>
67    where
68        T: Clone,
69    {
70        self.traverse_ref(
71            &mut |t, _state: &()| {
72                if let Some(s) = pred(t) {
73                    TraverseControl::Return(s)
74                } else {
75                    TraverseControl::Continue
76                }
77            },
78            &(),
79        )
80    }
81}
82
83/// Similar to [Traverse], but takes an additional AST allocator for AST components that require
84/// such an allocator in order to build the result.
85pub trait TraverseAlloc<'ast, T>: Sized {
86    /// Same as [Traverse::traverse], but takes an additional AST allocator.
87    fn traverse<F, E>(
88        self,
89        alloc: &'ast AstAlloc,
90        f: &mut F,
91        order: TraverseOrder,
92    ) -> Result<Self, E>
93    where
94        F: FnMut(T) -> Result<T, E>;
95
96    /// Same as [Traverse::traverse_ref], but takes an additional AST allocator.
97    ///
98    /// There is as small difference though: this function guarantees that the lifetime of the
99    /// references is bound to the lifetime of the AST allocator, which the signature in
100    /// [Traverse::traverse_ref] does not. This is useful e.g. in the LSP to extract references and
101    /// store them in separate data structure. We can guarantee that those reference won't be
102    /// dangling as long as the allocator is around.
103    fn traverse_ref<S, U>(
104        &'ast self,
105        f: &mut dyn FnMut(&'ast T, &S) -> TraverseControl<S, U>,
106        scope: &S,
107    ) -> Option<U>;
108
109    fn find_map<S>(&'ast self, mut pred: impl FnMut(&'ast T) -> Option<S>) -> Option<S>
110    where
111        T: Clone + 'ast,
112    {
113        self.traverse_ref(
114            &mut |t, _state: &()| {
115                if let Some(s) = pred(t) {
116                    TraverseControl::Return(s)
117                } else {
118                    TraverseControl::Continue
119                }
120            },
121            &(),
122        )
123    }
124}
125
126/// Takes an iterator whose item type implements [TraverseAlloc], traverse each element, and
127/// collect the result as a slice allocated via `alloc`.
128pub fn traverse_alloc_many<'ast, T, U, I, F, E>(
129    alloc: &'ast AstAlloc,
130    it: I,
131    f: &mut F,
132    order: TraverseOrder,
133) -> Result<&'ast [U], E>
134where
135    U: TraverseAlloc<'ast, T> + Sized + Allocable,
136    I: IntoIterator<Item = U>,
137    F: FnMut(T) -> Result<T, E>,
138{
139    let collected: Result<Vec<_>, E> = it
140        .into_iter()
141        .map(|elt| elt.traverse(alloc, f, order))
142        .collect();
143
144    Ok(alloc.alloc_many(collected?))
145}