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}