1
//! # tree-morph
2
//!
3
//! **Tree-morph is a library that helps you perform boilerplate-free generic tree transformations.**
4
//!
5
//! ## Quick Example
6
//!
7
//! In this simple example, we use tree-morph to calculate mathematical expressions using multiplication, addition and squaring.
8
//!
9
//! ```rust
10
1
//! use tree_morph::prelude::*;
11
//! use uniplate::derive::Uniplate;
12
//!
13
//! #[derive(Debug, Clone, PartialEq, Eq, Uniplate)]
14
//! #[uniplate()]
15
//! enum Expr {
16
//!     Add(Box<Expr>, Box<Expr>),
17
//!     Mul(Box<Expr>, Box<Expr>),
18
//!     Sqr(Box<Expr>, Box<Expr>),
19
//!     Val(i32),
20
//! }
21
//! ```
22
1
//! Tree-morph makes use of the [Uniplate crate](https://crates.io/crates/uniplate/0.2.1), which allows for boilerplate-free tree traversals. By recursively nesting these four expressions, we can build any mathematical expression involving addition, multiplication and raising to integer powers. For example, the expression ``(1+2)^2`` can be written as:
23
//!
24
//! ```rust
25
1
//! # use tree_morph::prelude::*;
26
//! # use uniplate::derive::Uniplate;
27
//! #
28
//! # #[derive(Debug, Clone, PartialEq, Eq, Uniplate)]
29
//! # #[uniplate()]
30
//! # enum Expr {
31
//! #     Add(Box<Expr>, Box<Expr>),
32
//! #     Mul(Box<Expr>, Box<Expr>),
33
//! #     Sqr(Box<Expr>),
34
//! #     Val(i32),
35
//! # }
36
//! let my_expression = Expr::Sqr(
37
1
//!     Box::new(Expr::Add(
38
1
//!         Box::new(Expr::Val(1)),
39
1
//!         Box::new(Expr::Val(2))
40
1
//!     ))
41
1
//! );
42
1
//! ```
43
1
//! Now we know how to create expressions, we have to also create rules that transform expressions. The following functions provide addition and multiplication rules for our tree.
44
//!```rust
45
1
//! # use tree_morph::prelude::*;
46
//! # use uniplate::derive::Uniplate;
47
//! #
48
//! # #[derive(Debug, Clone, PartialEq, Eq, Uniplate)]
49
//! # #[uniplate()]
50
//! # enum Expr {
51
//! #     Add(Box<Expr>, Box<Expr>),
52
//! #     Mul(Box<Expr>, Box<Expr>),
53
//! #     Sqr(Box<Expr>),
54
//! #     Val(i32),
55
//! # }
56
//!fn rule_eval_add(cmds: &mut Commands<Expr, i32>, subtree: &Expr, meta: &i32) -> Option<Expr> {
57
//!    if let Expr::Add(a, b) = subtree {
58
//!        if let (Expr::Val(a_v), Expr::Val(b_v)) = (a.as_ref(), b.as_ref()) {
59
//!            return Some(Expr::Val(a_v + b_v));
60
//!        }
61
//!    }
62
//!    None
63
//!}
64
//!
65
//!fn rule_eval_mul(cmds: &mut Commands<Expr, i32>, subtree: &Expr, meta: &i32) -> Option<Expr> {
66
//!    if let Expr::Mul(a, b) = subtree {
67
//!        if let (Expr::Val(a_v), Expr::Val(b_v)) = (a.as_ref(), b.as_ref()) {
68
//!            return Some(Expr::Val(a_v * b_v));
69
//!        }
70
//!    }
71
//!    None
72
//!}
73
//!```
74
1
//!We will talk about the the ``Commands<Expr, i32>`` and ``meta`` inputs later, but for now we view the add/mul rules as checking if a tree node has the right enum variant (in this case ``Expr::Add`` or ``Expr::Mul``) and children (two ``Expr::Val`` variants), and adding/multiplying if possible, otherwise retuning ``None``.
75
//!
76
//!We can defining the squaring rule in a similar way.
77
//!```rust
78
1
//! # use tree_morph::prelude::*;
79
//! # use uniplate::derive::Uniplate;
80
//! #
81
//! # #[derive(Debug, Clone, PartialEq, Eq, Uniplate)]
82
//! # #[uniplate()]
83
//! # enum Expr {
84
//! #     Add(Box<Expr>, Box<Expr>),
85
//! #     Mul(Box<Expr>, Box<Expr>),
86
//! #     Sqr(Box<Expr>),
87
//! #     Val(i32),
88
//! # }
89
//!fn rule_expand_sqr(cmds: &mut Commands<Expr, i32>, subtree: &Expr, meta: &i32) -> Option<Expr> {
90
//!    if let Expr::Sqr(expr) = subtree {
91
//!        return Some(Expr::Mul(
92
//!            Box::new(*expr.clone()),
93
//!            Box::new(*expr.clone())
94
//!        ));
95
//!    }
96
//!    None
97
//!}
98
//!```
99
1
//!Now we have everything in place to start using tree-morph to apply our transformation rules to evaluate expressions. The following ``#[test]`` block checks that ``my_expression`` does indeed hold a value of ``9``.
100
//!
101
//!```rust
102
1
//! # use tree_morph::prelude::*;
103
//! # use uniplate::derive::Uniplate;
104
//! #
105
//! # #[derive(Debug, Clone, PartialEq, Eq, Uniplate)]
106
//! # #[uniplate()]
107
//! # enum Expr {
108
//! #     Add(Box<Expr>, Box<Expr>),
109
//! #     Mul(Box<Expr>, Box<Expr>),
110
//! #     Sqr(Box<Expr>),
111
//! #     Val(i32),
112
//! # }
113
//! # fn rule_eval_add(cmds: &mut Commands<Expr, i32>, subtree: &Expr, meta: &i32) -> Option<Expr> {
114
//! #     if let Expr::Add(a, b) = subtree {
115
//! #         if let (Expr::Val(a_v), Expr::Val(b_v)) = (a.as_ref(), b.as_ref()) {
116
//! #             return Some(Expr::Val(a_v + b_v));
117
//! #         }
118
//! #     }
119
//! #     None
120
//! # }
121
//! #
122
//! # fn rule_eval_mul(cmds: &mut Commands<Expr, i32>, subtree: &Expr, meta: &i32) -> Option<Expr> {
123
//! #     if let Expr::Mul(a, b) = subtree {
124
//! #         if let (Expr::Val(a_v), Expr::Val(b_v)) = (a.as_ref(), b.as_ref()) {
125
//! #             return Some(Expr::Val(a_v * b_v));
126
//! #         }
127
//! #     }
128
//! #     None
129
//! # }
130
//! # fn rule_expand_sqr(cmds: &mut Commands<Expr, i32>, subtree: &Expr, meta: &i32) -> Option<Expr> {
131
//! #     if let Expr::Sqr(expr) = subtree {
132
//! #         return Some(Expr::Mul(
133
//! #             Box::new(*expr.clone()),
134
//! #             Box::new(*expr.clone())
135
//! #         ));
136
//! #     }
137
//! #     None
138
//! # }
139
//!#[test]
140
//!fn check_my_expression() {
141
//!    let my_expression = Expr::Sqr(Box::new(Expr::Add(
142
//!        Box::new(Expr::Val(1)),
143
//!        Box::new(Expr::Val(2)),
144
//!    )));
145
//!
146
//!    let (result, _) = morph(
147
//!        vec![rule_fns![rule_eval_add, rule_eval_mul, rule_expand_sqr]],
148
//!        tree_morph::helpers::select_panic,
149
//!        my_expression.clone(),
150
//!        0,
151
//!    );
152
//!    assert_eq!(result, Expr::Val(9));
153
//!}
154
//!```
155
1
//!The ``morph`` function is the core function of the crate, and handles the bulk application of transformation rules to the tree. We input the set of rules, a decision function, and some metadata, returning a ``(tree, metadata)`` tuple. Running ``cargo test --test file_name`` in a directory containing the file with this code will verify that tree-morph is indeed doing what it should do! In the following sections we explore some of tree-morph's features in more depth.
156
//!
157
//!## Metadata
158
//!Metadata refers to additional contextual information passed along during the tree transformation process. A simple usage of metadata is counting the number of transformation steps a process takes. We keep track of metadata via the ``Commands`` struct, which is used to capture any other side-effects to the transformation process apart from the pure rule changes. If, in our above example, we wanted to count the number of addition rule changes applies, we would first need to create a new struct to capture the metadata.
159
//!
160
//!```rust
161
1
//! // --snip--
162
//!struct Meta {
163
//!num_applications_addition: i32,
164
//!}
165
//! // --snip--
166
1
//!```
167
//!Also, until now the ``Commands`` object has held types ``<Expr, i32>``; in general, a commands object holds types ``<T, M>``, where `T` is the tree type, and `M` is the metadata type. To include our new struct ``Meta``, we need to adjust the types accordingly.
168
//! ```rust
169
1
//! # use tree_morph::prelude::*;
170
//! # use uniplate::derive::Uniplate;
171
//! #
172
//! # #[derive(Debug, Clone, PartialEq, Eq, Uniplate)]
173
//! # #[uniplate()]
174
//! # enum Expr {
175
//! #     Add(Box<Expr>, Box<Expr>),
176
//! #     Mul(Box<Expr>, Box<Expr>),
177
//! #     Sqr(Box<Expr>),
178
//! #     Val(i32),
179
//! # }
180
//!struct Meta {
181
//!num_applications_addition: i32,
182
//!}
183
//! fn rule_eval_add(cmds: &mut Commands<Expr, Meta>, subtree: &Expr, meta: &Meta) -> Option<Expr> {
184
//!     // --snip--
185
//! None
186
//! }
187
//!
188
//! fn rule_eval_mul(cmds: &mut Commands<Expr, Meta>, subtree: &Expr, meta: &Meta) -> Option<Expr> {
189
//!    // --snip--
190
//! None
191
//! }
192
//!
193
//! fn rule_expand_sqr(cmds: &mut Commands<Expr, Meta>, subtree: &Expr, meta: &Meta) -> Option<Expr> {
194
//!    // --snip--
195
//! None
196
//! }
197
//! ```
198
1
//! Now the function types make sense, to add one to ``num_applications_addition`` each time an addition rule is applied, we just need to add a metadata command to the ``Commands`` queue each time that a successful rule application is undertaken.
199
//!
200
//! ```rust
201
1
//! # use tree_morph::prelude::*;
202
//! # use uniplate::derive::Uniplate;
203
//! #
204
//! # #[derive(Debug, Clone, PartialEq, Eq, Uniplate)]
205
//! # #[uniplate()]
206
//! # enum Expr {
207
//! #     Add(Box<Expr>, Box<Expr>),
208
//! #     Mul(Box<Expr>, Box<Expr>),
209
//! #     Sqr(Box<Expr>),
210
//! #     Val(i32),
211
//! # }
212
//! # struct Meta {
213
//! # num_applications_addition: i32,
214
//! # }
215
//! fn rule_eval_add(cmds: &mut Commands<Expr, Meta>, subtree: &Expr, meta: &Meta) -> Option<Expr> {
216
//!     if let Expr::Add(a, b) = subtree {
217
//!         if let (Expr::Val(a_v), Expr::Val(b_v)) = (a.as_ref(), b.as_ref()) {
218
//!             cmds.mut_meta(Box::new(|m: &mut Meta| m.num_applications_addition += 1)); //new
219
//!             return Some(Expr::Val(a_v + b_v));
220
//!         }
221
//!     }
222
//!     None
223
//! }
224
//! ```
225
1
//! Now, each time that the addition rule is successfully applied, the value of num_applications_addition will increase by 1! We may also
226
//! choose to place the commands before the ``if`` block, as side-effects are only evaluated upon a successful rule update. This can make the code in the block a little easier to read. The following is
227
//! completely equivalent to the above code.
228
//!
229
//! ```rust
230
1
//! # use tree_morph::prelude::*;
231
//! # use uniplate::derive::Uniplate;
232
//! #
233
//! # #[derive(Debug, Clone, PartialEq, Eq, Uniplate)]
234
//! # #[uniplate()]
235
//! # enum Expr {
236
//! #     Add(Box<Expr>, Box<Expr>),
237
//! #     Mul(Box<Expr>, Box<Expr>),
238
//! #     Sqr(Box<Expr>),
239
//! #     Val(i32),
240
//! # }
241
//! # struct Meta {
242
//! # num_applications_addition: i32,
243
//! # }
244
//! fn rule_eval_add(cmds: &mut Commands<Expr, Meta>, subtree: &Expr, meta: &Meta) -> Option<Expr> {
245
//!     cmds.mut_meta(Box::new(|m: &mut Meta| m.num_applications_addition += 1)); //new location
246
//!     if let Expr::Add(a, b) = subtree {
247
//!         if let (Expr::Val(a_v), Expr::Val(b_v)) = (a.as_ref(), b.as_ref()) {
248
//!             return Some(Expr::Val(a_v + b_v));
249
//!         }
250
//!     }
251
//!     None
252
//! }
253
//! ```
254
1
//! The following test block verifies that two addition operations take place.
255
//! ```rust
256
1
//! # use tree_morph::prelude::*;
257
//! # use uniplate::derive::Uniplate;
258
//! #
259
//! # #[derive(Debug, Clone, PartialEq, Eq, Uniplate)]
260
//! # #[uniplate()]
261
//! # enum Expr {
262
//! #     Add(Box<Expr>, Box<Expr>),
263
//! #     Mul(Box<Expr>, Box<Expr>),
264
//! #     Sqr(Box<Expr>),
265
//! #     Val(i32),
266
//! # }
267
//! # struct Meta {
268
//! # num_applications_addition: i32,
269
//! # }
270
//! # fn rule_eval_add(cmds: &mut Commands<Expr, Meta>, subtree: &Expr, meta: &Meta) -> Option<Expr> {
271
//! #     if let Expr::Add(a, b) = subtree {
272
//! #         if let (Expr::Val(a_v), Expr::Val(b_v)) = (a.as_ref(), b.as_ref()) {
273
//! #             cmds.mut_meta(Box::new(|m: &mut Meta| m.num_applications_addition += 1)); //new
274
//! #             return Some(Expr::Val(a_v + b_v));
275
//! #         }
276
//! #     }
277
//! #     None
278
//! # }
279
//! # fn rule_eval_mul(cmds: &mut Commands<Expr, Meta>, subtree: &Expr, meta: &Meta) -> Option<Expr> {
280
//! #     if let Expr::Mul(a, b) = subtree {
281
//! #         if let (Expr::Val(a_v), Expr::Val(b_v)) = (a.as_ref(), b.as_ref()) {
282
//! #             return Some(Expr::Val(a_v * b_v));
283
//! #         }
284
//! #     }
285
//! #     None
286
//! # }
287
//! # fn rule_expand_sqr(cmds: &mut Commands<Expr, Meta>, subtree: &Expr, meta: &Meta) -> Option<Expr> {
288
//! #     if let Expr::Sqr(expr) = subtree {
289
//! #         return Some(Expr::Mul(
290
//! #             Box::new(*expr.clone()),
291
//! #             Box::new(*expr.clone())
292
//! #         ));
293
//! #     }
294
//! #     None
295
//! # }
296
//! #[test]
297
//! fn number_of_operations() {
298
//!     let my_expression = Expr::Sqr(Box::new(Expr::Add(
299
//!         Box::new(Expr::Val(1)),
300
//!         Box::new(Expr::Val(2)),
301
//!     )));
302
//!
303
//!     let metadata = Meta {
304
//!         num_applications_addition: 0,
305
//!     };
306
//!     let (result, metaresult) = morph(
307
//!         vec![rule_fns![rule_eval_add, rule_eval_mul, rule_expand_sqr]],
308
//!         tree_morph::helpers::select_panic,
309
//!         my_expression.clone(),
310
//!         metadata,
311
//!     );
312
//!     assert_eq!(metaresult.num_applications_addition, 2);
313
//! }
314
//! ```
315
1
//! Running ``cargo test --test file_name`` in a directory containing the file with this code will verify that indeed two addition operations are successfully undertaken.
316
//!
317
//! But wait! The original problem was solving ``(1+2)^2``, why have two addition operations been recorded? The answer is in how we grouped the rules together in the ``morph`` function.
318
//!
319
//! ## Rule Groups
320
//!
321
//! In the ``morph`` function, in the first input, rust expects a **rule group** object of type ``Vec<Vec<R>>``, where `R` is the `Rule` trait. We can use the macro ``rule_fns!`` to simultaneously handle giving functions the ``Rule`` trait and putting them inside vectors. Rule groups are a powerful feature of tree-morph, and allow for a priority system whereby some rules are attempted before others. For example, if we have the rule grouping ``vec![vec![rule1,rule2],vec![rule3]]``, ``morph`` will start by visiting the first node and trying to apply rule1 and rule2. If unsuccessful, ``morph`` will then move onto the second node, trying rule1 and rule2 again. Note that since rule3 is in a lower priority grouping, tree-morph will not try rule3 on the first node until rule1 and rule2 have been attempted on the **entire** tree.
322
//!
323
//! It is now clear why ``assert_eq!(metaresult.num_applications_addition, 2);`` holds above. Because rule ``rule_expand_sqr`` was in the same grouping as all the other rules, tree-morph applied the rule before the addition node was ever reached. To increase the efficiency the solving algorithm, we can assign the ``rule_expand_sqr`` with a lower priority.
324
//! ```rust
325
1
//! # use tree_morph::prelude::*;
326
//! # use uniplate::derive::Uniplate;
327
//! #
328
//! # #[derive(Debug, Clone, PartialEq, Eq, Uniplate)]
329
//! # #[uniplate()]
330
//! # enum Expr {
331
//! #     Add(Box<Expr>, Box<Expr>),
332
//! #     Mul(Box<Expr>, Box<Expr>),
333
//! #     Sqr(Box<Expr>),
334
//! #     Val(i32),
335
//! # }
336
//! # struct Meta {
337
//! # num_applications_addition: i32,
338
//! # }
339
//! # fn rule_eval_add(cmds: &mut Commands<Expr, Meta>, subtree: &Expr, meta: &Meta) -> Option<Expr> {
340
//! #     if let Expr::Add(a, b) = subtree {
341
//! #         if let (Expr::Val(a_v), Expr::Val(b_v)) = (a.as_ref(), b.as_ref()) {
342
//! #             cmds.mut_meta(Box::new(|m: &mut Meta| m.num_applications_addition += 1)); //new
343
//! #             return Some(Expr::Val(a_v + b_v));
344
//! #         }
345
//! #     }
346
//! #     None
347
//! # }
348
//! # fn rule_eval_mul(cmds: &mut Commands<Expr, Meta>, subtree: &Expr, meta: &Meta) -> Option<Expr> {
349
//! #     if let Expr::Mul(a, b) = subtree {
350
//! #         if let (Expr::Val(a_v), Expr::Val(b_v)) = (a.as_ref(), b.as_ref()) {
351
//! #             return Some(Expr::Val(a_v * b_v));
352
//! #         }
353
//! #     }
354
//! #     None
355
//! # }
356
//! # fn rule_expand_sqr(cmds: &mut Commands<Expr, Meta>, subtree: &Expr, meta: &Meta) -> Option<Expr> {
357
//! #     if let Expr::Sqr(expr) = subtree {
358
//! #         return Some(Expr::Mul(
359
//! #             Box::new(*expr.clone()),
360
//! #             Box::new(*expr.clone())
361
//! #         ));
362
//! #     }
363
//! #     None
364
//! # }
365
//! #[test]
366
//! fn number_of_operations() {
367
//! #      let my_expression = Expr::Sqr(Box::new(Expr::Add(
368
//! #          Box::new(Expr::Val(1)),
369
//! #          Box::new(Expr::Val(2)),
370
//! #      )));
371
//! #
372
//! #      let metadata = Meta {
373
//! #          num_applications_addition: 0,
374
//! #      };
375
//!     // --snip--
376
//!     let (result, metaresult) = morph(
377
//!         vec![
378
//!             rule_fns![rule_eval_add, rule_eval_mul],
379
//!             rule_fns![rule_expand_sqr],
380
//!         ], //new
381
//!         tree_morph::helpers::select_panic,
382
//!         my_expression.clone(),
383
//!         metadata,
384
//!     );
385
//!     // --snip--
386
//! }
387
//! ```
388
1
//! Now that ``rule_expand_sqr`` has a lower priority, the addition operation will be applied first, and hence ``metaresult.num_applications_addition`` should equal 1. If we make the following change to the test, we can verify this directly.
389
//! ```rust
390
1
//! # use tree_morph::prelude::*;
391
//! # use uniplate::derive::Uniplate;
392
//! #
393
//! # #[derive(Debug, Clone, PartialEq, Eq, Uniplate)]
394
//! # #[uniplate()]
395
//! # enum Expr {
396
//! #     Add(Box<Expr>, Box<Expr>),
397
//! #     Mul(Box<Expr>, Box<Expr>),
398
//! #     Sqr(Box<Expr>),
399
//! #     Val(i32),
400
//! # }
401
//! # struct Meta {
402
//! # num_applications_addition: i32,
403
//! # }
404
//! # fn rule_eval_add(cmds: &mut Commands<Expr, Meta>, subtree: &Expr, meta: &Meta) -> Option<Expr> {
405
//! #     if let Expr::Add(a, b) = subtree {
406
//! #         if let (Expr::Val(a_v), Expr::Val(b_v)) = (a.as_ref(), b.as_ref()) {
407
//! #             cmds.mut_meta(Box::new(|m: &mut Meta| m.num_applications_addition += 1)); //new
408
//! #             return Some(Expr::Val(a_v + b_v));
409
//! #         }
410
//! #     }
411
//! #     None
412
//! # }
413
//! # fn rule_eval_mul(cmds: &mut Commands<Expr, Meta>, subtree: &Expr, meta: &Meta) -> Option<Expr> {
414
//! #     if let Expr::Mul(a, b) = subtree {
415
//! #         if let (Expr::Val(a_v), Expr::Val(b_v)) = (a.as_ref(), b.as_ref()) {
416
//! #             return Some(Expr::Val(a_v * b_v));
417
//! #         }
418
//! #     }
419
//! #     None
420
//! # }
421
//! # fn rule_expand_sqr(cmds: &mut Commands<Expr, Meta>, subtree: &Expr, meta: &Meta) -> Option<Expr> {
422
//! #     if let Expr::Sqr(expr) = subtree {
423
//! #         return Some(Expr::Mul(
424
//! #             Box::new(*expr.clone()),
425
//! #             Box::new(*expr.clone())
426
//! #         ));
427
//! #     }
428
//! #     None
429
//! # }
430
//! #[test]
431
//! fn number_of_operations() {
432
//! #      let my_expression = Expr::Sqr(Box::new(Expr::Add(
433
//! #          Box::new(Expr::Val(1)),
434
//! #          Box::new(Expr::Val(2)),
435
//! #      )));
436
//! #
437
//! #      let metadata = Meta {
438
//! #          num_applications_addition: 0,
439
//! #      };
440
//!     // --snip--
441
//!     let (result, metaresult) = morph(
442
//!         vec![
443
//!             rule_fns![rule_eval_add, rule_eval_mul],
444
//!             rule_fns![rule_expand_sqr],
445
//!         ], //new
446
//!         tree_morph::helpers::select_panic,
447
//!         my_expression.clone(),
448
//!         metadata,
449
//!     );
450
//!        assert_eq!(metaresult.num_applications_addition, 1); //new, only one addition performed
451
//! }
452
//! ```
453
1
//! ## Selector Functions
454
//! The second input in the ``morph`` function is a **selector function** from the ``tree_morph::helpers`` crate. Selector functions are what tree-morph uses if there is ever ambiguity in what rule to apply. Ambiguity can arise when two rules are both applicable at the same time, and have the same priority as assigned via rule groupings. We have various ways to deal with this in tree-morph, and in our example we have used ``select_panic``, which causes rust to `panic!` if there is ever ambiguity.
455
//!
456
//! ## Commands
457
//! We have previously shown how a ``Commands`` struct can be used to store metadata-updating rules. It is also possible to store entire **tree transformations** too. This might be useful for scenarios in which you want a tree transformation to occur immediately after some successful rule change.
458
//!
459
//! ## Acknowledgements
460
//! Finish....
461

            
462
#![warn(missing_docs)]
463

            
464
mod commands;
465
mod engine;
466
pub mod helpers;
467
mod rule;
468
mod update;
469

            
470
/// Re-exported functions and types for convenience.
471
pub mod prelude {
472
    use super::*;
473

            
474
    pub use crate::rule_fns;
475
    pub use commands::Commands;
476
    pub use engine::morph;
477
    pub use helpers::select_first;
478
    pub use rule::{Rule, RuleFn};
479
    pub use update::Update;
480
}
481

            
482
pub use commands::Commands;
483
pub use engine::morph;
484
pub use rule::{Rule, RuleFn};
485
pub use update::Update;