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