Skip to main content

time_macros/format_description/public/
optimizing.rs

1//! Optimization for format descriptions.
2//!
3//! The tree of all items is walked recursively and optimized in-place. Optimizations are ordered so
4//! that their effects are consumed by later optimizations in a single pass. Children are optimized
5//! before their parent, so sibling-level interactions are the only concern at each level.
6//!
7//! Each optimization function accepts `self` mutably and returns whether it modified the tree. Note
8//! that optimizations *must not* affect runtime behavior in terms of formatting output, accepted
9//! input when parsing, or output from the parser.
10
11use std::mem;
12
13use super::{Component, OwnedFormatItem, OwnedFormatItemInner};
14
15impl OwnedFormatItem {
16    pub(crate) fn optimize(&mut self) {
17        self.inner.optimize();
18    }
19}
20
21impl OwnedFormatItemInner {
22    pub(crate) fn optimize(&mut self) {
23        // Walk the tree and optimize all children.
24        match self {
25            Self::Literal(_) | Self::StringLiteral(_) | Self::Component(_) => {}
26            Self::Compound(items) | Self::First(items) => {
27                for item in items {
28                    item.optimize();
29                }
30            }
31            Self::Optional { format: _, item } => item.optimize(),
32        }
33
34        // Run all optimizations in dependency order: group only creates opportunities that later
35        // passes consume. Passes that mutate the node type (e.g., uplifting an optional) run first,
36        // followed by structural passes (unnesting, cleanup), with consuming passes (merging,
37        // trivial unwrapping) last.
38        let passes = [
39            // type-changing passes
40            Self::only_formatting_uplift_optional,
41            Self::only_formatting_uplift_first,
42            Self::only_formatting_eliminate_end,
43            // structural passes: inline nested containers, remove no-op children
44            Self::unnest_nested_compounds,
45            Self::unnest_nested_first,
46            Self::compound_containing_empty_string,
47            // consuming passes: merge siblings, unwrap trivial wrappers
48            Self::merge_consecutive_literals,
49            Self::unnest_trivial_compounds,
50            Self::unnest_first_only_one,
51        ];
52        for pass in passes {
53            pass(self);
54        }
55    }
56
57    const fn no_op() -> Self {
58        Self::StringLiteral(String::new())
59    }
60
61    /// When there are multiple consecutive literals, they can be merged into a single literal.
62    ///
63    /// As there are both UTF-8 and non-UTF-8 literals, the output is UTF-8 if and only if both
64    /// literals are as well.
65    fn merge_consecutive_literals(&mut self) -> bool {
66        let Self::Compound(items) = self else {
67            return false;
68        };
69
70        let mut something_was_changed = false;
71        let mut idx = 1;
72        while idx < items.len() {
73            // Safety: `idx - 1` is not equal to `idx` and both are in-bounds.
74            let pair = unsafe { items.get_disjoint_unchecked_mut([idx - 1, idx]) };
75
76            match pair {
77                [Self::Literal(a), Self::Literal(b)] => {
78                    a.append(b);
79                    items.remove(idx);
80                    something_was_changed = true;
81                }
82                [Self::Literal(a), Self::StringLiteral(b)] => {
83                    a.extend(b.as_bytes());
84                    items.remove(idx);
85                    something_was_changed = true;
86                }
87                [item @ Self::StringLiteral(_), Self::Literal(b)] => {
88                    let Self::StringLiteral(a) = item else {
89                        unreachable!()
90                    };
91                    let mut bytes = a.as_bytes().to_vec();
92                    bytes.append(b);
93                    *item = Self::Literal(bytes);
94                    items.remove(idx);
95                    something_was_changed = true;
96                }
97                [Self::StringLiteral(a), Self::StringLiteral(b)] => {
98                    a.push_str(b);
99                    items.remove(idx);
100                    something_was_changed = true;
101                }
102                _ => idx += 1,
103            }
104        }
105
106        something_was_changed
107    }
108
109    /// When a compound item only contains a single item, it can be replaced with that item.
110    fn unnest_trivial_compounds(&mut self) -> bool {
111        if let Self::Compound(items) = self
112            && items.len() == 1
113            && let Some(item) = items.pop()
114        {
115            *self = item;
116            true
117        } else {
118            false
119        }
120    }
121
122    /// When a compound item contains another compound item, the latter can be inlined into the
123    /// former.
124    fn unnest_nested_compounds(&mut self) -> bool {
125        let Self::Compound(items) = self else {
126            return false;
127        };
128
129        let mut idx = 0;
130        let mut something_was_changed = false;
131        while idx < items.len() {
132            if let Self::Compound(inner_items) = &mut items[idx] {
133                let inner_items = mem::take(inner_items);
134                items.splice(idx..=idx, inner_items).for_each(drop);
135                something_was_changed = true;
136            } else {
137                idx += 1;
138            }
139        }
140
141        something_was_changed
142    }
143
144    /// When a first item only contains a single item, it can be replaced with that item.
145    fn unnest_first_only_one(&mut self) -> bool {
146        if let Self::First(items) = self
147            && items.len() == 1
148            && let Some(item) = items.pop()
149        {
150            *self = item;
151            true
152        } else {
153            false
154        }
155    }
156
157    /// When a first item contains another first item, the latter can be inlined into the former.
158    fn unnest_nested_first(&mut self) -> bool {
159        let Self::First(items) = self else {
160            return false;
161        };
162
163        let mut idx = 0;
164        let mut something_was_changed = false;
165        while idx < items.len() {
166            if let Self::First(inner_items) = &mut items[idx] {
167                let inner_items = mem::take(inner_items);
168                items.splice(idx..=idx, inner_items).for_each(drop);
169                something_was_changed = true;
170            } else {
171                idx += 1;
172            }
173        }
174
175        something_was_changed
176    }
177
178    /// When formatting is enabled but parsing is not, the behavior of an optional item is known
179    /// ahead of time. If it is formatted, the optional item can be replaced with its inner item. If
180    /// it is not formatted, it can be replace with a no-op (that will likely be removed in a later
181    /// pass).
182    fn only_formatting_uplift_optional(&mut self) -> bool {
183        // This optimization only makes sense when *only* formatting is enabled, as otherwise the
184        // optional item may be needed for parsing.
185        if !cfg!(feature = "formatting") || cfg!(feature = "parsing") {
186            return false;
187        }
188
189        let Self::Optional { format, item } = self else {
190            return false;
191        };
192
193        let item = if *format {
194            mem::replace(item.as_mut(), Self::no_op())
195        } else {
196            Self::no_op()
197        };
198
199        *self = item;
200        true
201    }
202
203    /// When formatting is enabled but parsing is not, the behavior of a first item is known ahead
204    /// of time. It can be replaced with its first item, as the first item will always be the
205    /// one that is formatted.
206    fn only_formatting_uplift_first(&mut self) -> bool {
207        // This optimization only makes sense when *only* formatting is enabled, as otherwise the
208        // remaining items may be needed for parsing.
209        if !cfg!(feature = "formatting") || cfg!(feature = "parsing") {
210            return false;
211        }
212
213        let Self::First(items) = self else {
214            return false;
215        };
216
217        *self = items.remove(0);
218        true
219    }
220
221    fn only_formatting_eliminate_end(&mut self) -> bool {
222        // This optimization only makes sense when *only* formatting is enabled, as otherwise the
223        // remaining items may be needed for parsing.
224        if !cfg!(feature = "formatting") || cfg!(feature = "parsing") {
225            return false;
226        }
227
228        if let Self::Component(Component::End(_)) = self {
229            *self = Self::no_op();
230            true
231        } else {
232            false
233        }
234    }
235
236    /// When a compound item contains an empty string literal, it can be removed as it has no
237    /// effect.
238    fn compound_containing_empty_string(&mut self) -> bool {
239        let Self::Compound(items) = self else {
240            return false;
241        };
242
243        let mut idx = 0;
244        let mut something_was_changed = false;
245        while idx < items.len() {
246            if let Self::StringLiteral(s) = &items[idx]
247                && s.is_empty()
248            {
249                items.remove(idx);
250                something_was_changed = true;
251            } else {
252                idx += 1;
253            }
254        }
255
256        something_was_changed
257    }
258}