markdown_that/common/
ruler.rs

1//! Plugin manager with dependency resolution.
2
3use educe::Educe;
4use std::collections::{HashMap, HashSet};
5use std::fmt::Debug;
6use std::hash::Hash;
7use std::slice::Iter;
8use std::sync::OnceLock;
9
10///
11/// Ruler allows you to implement a plugin system with dependency management and ensure that
12/// your dependencies are called in the correct order.
13///
14/// You can use it like this:
15/// ```
16/// use markdown_that::common::ruler::Ruler;
17///
18/// // this example prints "[ hello, world! ]",
19/// // where each token is printed by a separate closure
20/// let mut chain = Ruler::<&str, fn (&mut String)>::new();
21///
22/// // define rules printing "hello" and "world"
23/// chain.add("hello", |s| s.push_str("hello"));
24/// chain.add("world", |s| s.push_str("world"));
25///
26/// // an open bracket should be before "hello" and closing one after "world"
27/// chain.add("open_bracket", |s| s.push_str("[ ")).before("hello");
28/// chain.add("close_bracket", |s| s.push_str(" ]")).after("world");
29///
30/// // between "hello" and "world" we shall have a comma
31/// chain.add("comma", |s| s.push_str(", ")).after("hello").before("world");
32///
33/// // after "world" we should have "!" as a first rule, but ensure "world" exists first
34/// chain.add("bang", |s| s.push_str("!")).require("world").after("world").before_all();
35///
36/// // now we run this chain
37/// let mut result = String::new();
38/// for f in chain.iter() { f(&mut result); }
39/// assert_eq!(result, "[ hello, world! ]");
40/// ```
41///
42/// This data structure contains any number of elements (M, T), where T is any type and
43/// M (mark) is its identifier.
44///
45///  - `M` is used for ordering and dependency checking, it must implement `Eq + Copy + Hash + Debug.
46/// Common choices for `M` are `u32`, `&'static str`, or a special `Symbol` type
47/// designed for this purpose.
48///
49///  - `T` is any user-defined type. It's usually a function or boxed trait.
50///
51pub struct Ruler<M, T> {
52    deps: Vec<RuleItem<M, T>>,
53    compiled: OnceLock<(Vec<usize>, Vec<T>)>,
54}
55
56impl<M, T> Ruler<M, T> {
57    pub fn new() -> Self {
58        Self::default()
59    }
60}
61
62impl<M: Eq + Hash + Copy + Debug, T: Clone> Ruler<M, T> {
63    /// Add a new rule identified by `mark` with payload `value`.
64    pub fn add(&mut self, mark: M, value: T) -> &mut RuleItem<M, T> {
65        self.compiled = OnceLock::new();
66        let dep = RuleItem::new(mark, value);
67        self.deps.push(dep);
68        self.deps.last_mut().unwrap()
69    }
70
71    /// Remove all rules identified by `mark`.
72    pub fn remove(&mut self, mark: M) {
73        self.deps.retain(|dep| !dep.marks.contains(&mark));
74    }
75
76    /// Check if there are any rules identified by `mark`.
77    pub fn contains(&mut self, mark: M) -> bool {
78        self.deps.iter().any(|dep| dep.marks.contains(&mark))
79    }
80
81    /// Ordered iteration through rules.
82    #[inline]
83    pub fn iter(&self) -> Iter<T> {
84        self.compiled.get_or_init(|| self.compile()).1.iter()
85    }
86
87    fn compile(&self) -> (Vec<usize>, Vec<T>) {
88        // ID -> [RuleItem index]
89        let mut idhash = HashMap::<M, Vec<usize>>::new();
90
91        // RuleItem index -> [RuleItem index] - dependency graph, None if already inserted
92        let mut deps_graph = vec![HashSet::new(); self.deps.len()];
93
94        // additional level of indirection that takes into account item priority
95        let mut deps_order = vec![];
96        let mut beforeall_len = 0;
97        let mut afterall_len = 0;
98
99        // compiled result
100        let mut result = vec![];
101        let mut result_idx = vec![];
102
103        // track which rules have been added already
104        let mut deps_inserted = vec![false; self.deps.len()];
105        let mut deps_remaining = self.deps.len();
106
107        for (idx, dep) in self.deps.iter().enumerate() {
108            match dep.prio {
109                RuleItemPriority::Normal => {
110                    deps_order.insert(deps_order.len() - afterall_len, idx);
111                }
112                RuleItemPriority::BeforeAll => {
113                    deps_order.insert(beforeall_len, idx);
114                    beforeall_len += 1;
115                }
116                RuleItemPriority::AfterAll => {
117                    deps_order.insert(deps_order.len(), idx);
118                    afterall_len += 1;
119                }
120            }
121            for mark in &dep.marks {
122                idhash.entry(*mark).or_default().push(idx);
123            }
124        }
125
126        // build dependency graph, replacing all after's with before's,
127        // i.e. B.after(A) -> A.before(B)
128        for idx in deps_order.iter().copied() {
129            let dep = self.deps.get(idx).unwrap();
130            for constraint in &dep.cons {
131                match constraint {
132                    RuleItemConstraint::Before(v) => {
133                        for depidx in idhash.entry(*v).or_default().iter() {
134                            deps_graph.get_mut(*depidx).unwrap().insert(idx);
135                        }
136                    }
137                    RuleItemConstraint::After(v) => {
138                        for depidx in idhash.entry(*v).or_default().iter() {
139                            deps_graph.get_mut(idx).unwrap().insert(*depidx);
140                        }
141                    }
142                    RuleItemConstraint::Require(v) => {
143                        assert!(
144                            idhash.contains_key(v),
145                            "missing dependency: {:?} requires {:?}",
146                            dep.marks.first().unwrap(),
147                            v
148                        );
149                    }
150                }
151            }
152        }
153
154        // now go through the deps and push whatever doesn't have any
155        'outer: while deps_remaining > 0 {
156            for idx in deps_order.iter().copied() {
157                let inserted = deps_inserted.get_mut(idx).unwrap();
158                if *inserted {
159                    continue;
160                }
161
162                let dlist = deps_graph.get(idx).unwrap();
163                if dlist.is_empty() {
164                    let dep = self.deps.get(idx).unwrap();
165                    result.push(dep.value.clone());
166                    result_idx.push(idx);
167                    *inserted = true;
168                    deps_remaining -= 1;
169                    for d in deps_graph.iter_mut() {
170                        d.remove(&idx);
171                    }
172                    continue 'outer;
173                }
174            }
175
176            #[cfg(debug_assertions)]
177            {
178                // check cycles in the dependency graph;
179                // this is very suboptimal, but only used to generate a nice panic message.
180                // in release mode we'll just simply panic
181                for idx in deps_order.iter().copied() {
182                    let mut seen = HashMap::new();
183                    let mut vec = vec![idx];
184                    while let Some(didx) = vec.pop() {
185                        let dlist = deps_graph.get(didx).unwrap();
186                        for x in dlist.iter() {
187                            if seen.contains_key(x) {
188                                continue;
189                            }
190                            vec.push(*x);
191                            seen.insert(*x, didx);
192                            if *x == idx {
193                                let mut backtrack = vec![];
194                                let mut curr = idx;
195                                while !backtrack.contains(&curr) {
196                                    backtrack.push(curr);
197                                    curr = *seen.get(&curr).unwrap();
198                                }
199                                backtrack.push(curr);
200                                let path = backtrack
201                                    .iter()
202                                    .rev()
203                                    .map(|x| {
204                                        format!(
205                                            "{:?}",
206                                            self.deps.get(*x).unwrap().marks.first().unwrap()
207                                        )
208                                    })
209                                    .collect::<Vec<String>>()
210                                    .join(" < ");
211                                panic!("cyclic dependency: {}", path);
212                            }
213                        }
214                    }
215                }
216            }
217
218            // if you see this in debug mode, report it as a bug
219            panic!("cyclic dependency: (use debug mode for more details)");
220        }
221
222        (result_idx, result)
223    }
224}
225
226impl<M: Eq + Hash + Copy + Debug, T: Clone> Debug for Ruler<M, T> {
227    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
228        let vec: Vec<(usize, M)> = self
229            .compiled
230            .get_or_init(|| self.compile())
231            .0
232            .iter()
233            .map(|idx| (*idx, *self.deps.get(*idx).unwrap().marks.first().unwrap()))
234            .collect();
235
236        f.debug_struct("Ruler")
237            .field("deps", &self.deps)
238            .field("compiled", &vec)
239            .finish()
240    }
241}
242
243impl<M, T> Default for Ruler<M, T> {
244    fn default() -> Self {
245        Self {
246            deps: Vec::new(),
247            compiled: OnceLock::new(),
248        }
249    }
250}
251
252///
253/// Result of [Ruler::add](Ruler::add), allows customizing the position of each rule.
254///
255#[derive(Educe)]
256#[educe(Debug)]
257pub struct RuleItem<M, T> {
258    marks: Vec<M>,
259    #[educe(Debug(ignore))]
260    value: T,
261    prio: RuleItemPriority,
262    cons: Vec<RuleItemConstraint<M>>,
263}
264
265impl<M, T> RuleItem<M, T> {
266    fn new(mark: M, value: T) -> Self {
267        Self {
268            marks: vec![mark],
269            value,
270            prio: RuleItemPriority::Normal,
271            cons: vec![],
272        }
273    }
274}
275
276impl<M: Copy, T> RuleItem<M, T> {
277    /// Make sure this rule will be inserted before any rule defined by `mark` (if such a rule exists).
278    /// ```
279    /// use markdown_that::common::ruler::Ruler;
280    /// let mut chain = Ruler::<&str, fn (&mut String)>::new();
281    ///
282    /// chain.add("a", |s| s.push_str("bar"));
283    /// chain.add("b", |s| s.push_str("foo")).before("a");
284    ///
285    /// let mut result = String::new();
286    /// for f in chain.iter() { f(&mut result); }
287    /// assert_eq!(result, "foobar");
288    /// ```
289    pub fn before(&mut self, mark: M) -> &mut Self {
290        self.cons.push(RuleItemConstraint::Before(mark));
291        self
292    }
293
294    /// Make sure this rule will be inserted after any rule defined by `mark` (if such a rule exists).
295    /// Similar to [RuleItem::before](RuleItem::before).
296    pub fn after(&mut self, mark: M) -> &mut Self {
297        self.cons.push(RuleItemConstraint::After(mark));
298        self
299    }
300
301    /// This rule will be inserted as early as possible, while still taking into account dependencies,
302    /// i.e. `.after(X).before_all()` causes this to be first rule after X.
303    /// ```
304    /// use markdown_that::common::ruler::Ruler;
305    /// let mut chain = Ruler::<&str, fn (&mut String)>::new();
306    ///
307    /// chain.add("a", |s| s.push_str("A"));
308    /// chain.add("c", |s| s.push_str("C")).after("a");
309    /// chain.add("b", |s| s.push_str("B")).after("a").before_all();
310    ///
311    /// let mut result = String::new();
312    /// for f in chain.iter() { f(&mut result); }
313    /// // without before_all order will be ACB
314    /// assert_eq!(result, "ABC");
315    /// ```
316    pub fn before_all(&mut self) -> &mut Self {
317        self.prio = RuleItemPriority::BeforeAll;
318        self
319    }
320
321    /// This rule will be inserted as late as possible, while still taking into account dependencies,
322    /// i.e. `.before(X).after_all()` causes this to be the last rule before X.
323    /// Similar to [RuleItem::before_all](RuleItem::before_all).
324    pub fn after_all(&mut self) -> &mut Self {
325        self.prio = RuleItemPriority::AfterAll;
326        self
327    }
328
329    /// Add another auxiliary identifier to this rule. It can be used to group together multiple
330    /// rules with similar functionality.
331    /// ```
332    /// use markdown_that::common::ruler::Ruler;
333    /// let mut chain = Ruler::<&str, fn (&mut String)>::new();
334    ///
335    /// chain.add("b", |s| s.push_str("B")).alias("BorC");
336    /// chain.add("c", |s| s.push_str("C")).alias("BorC");
337    /// chain.add("a", |s| s.push_str("A")).before("BorC");
338    ///
339    /// let mut result = String::new();
340    /// for f in chain.iter() { f(&mut result); }
341    /// assert_eq!(result, "ABC");
342    /// ```
343    pub fn alias(&mut self, mark: M) -> &mut Self {
344        self.marks.push(mark);
345        self
346    }
347
348    /// Require another rule identified by `mark`, panic if not found.
349    pub fn require(&mut self, mark: M) -> &mut Self {
350        self.cons.push(RuleItemConstraint::Require(mark));
351        self
352    }
353}
354
355#[derive(Debug)]
356enum RuleItemConstraint<M> {
357    Before(M),
358    After(M),
359    Require(M),
360}
361
362#[derive(Debug)]
363enum RuleItemPriority {
364    Normal,
365    BeforeAll,
366    AfterAll,
367}
368
369#[cfg(test)]
370mod tests {
371    use super::Ruler;
372
373    #[test]
374    #[should_panic(expected = r#"cyclic dependency: "A" < "B" < "C" < "D" < "E" < "F" < "A""#)]
375    #[cfg(debug_assertions)]
376    fn cyclic_dependency_debug() {
377        let mut r = Ruler::new();
378        r.add("%", ()).after("D");
379        r.add("A", ()).after("B");
380        r.add("E", ()).after("F");
381        r.add("C", ()).after("D");
382        r.add("B", ()).after("C");
383        r.add("D", ()).after("E");
384        r.add("F", ()).after("A");
385        r.compile();
386    }
387
388    #[test]
389    #[should_panic(expected = r#"cyclic dependency"#)]
390    fn cyclic_dependency() {
391        let mut r = Ruler::new();
392        r.add("A", ()).after("B");
393        r.add("B", ()).after("C");
394        r.add("C", ()).after("A");
395        r.compile();
396    }
397
398    #[test]
399    #[should_panic(expected = r#"missing dependency: "C" requires "Z"#)]
400    fn missing_require() {
401        let mut r = Ruler::new();
402        r.add("A", ());
403        r.add("B", ()).require("A");
404        r.add("C", ()).require("Z");
405        r.compile();
406    }
407}