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.
1011use std::convert::identity;
12use std::mem;
1314use super::{Component, OwnedFormatItem, OwnedFormatItemInner};
1516impl OwnedFormatItem {
17pub(crate) fn optimize(&mut self) {
18self.inner.optimize();
19 }
20}
2122impl OwnedFormatItemInner {
23pub(crate) fn optimize(&mut self) {
24let passes = [
25Self::merge_consecutive_literals,
26Self::unnest_trivial_compounds,
27Self::unnest_nested_compounds,
28Self::unnest_first_only_one,
29Self::unnest_nested_first,
30Self::only_formatting_uplift_optional,
31Self::only_formatting_uplift_first,
32Self::only_formatting_eliminate_end,
33Self::compound_containing_empty_string,
34 ];
3536// Walk the tree and optimize all children.
37match self {
38Self::Literal(_) | Self::StringLiteral(_) | Self::Component(_) => {}
39Self::Compound(items) | Self::First(items) => {
40for item in items {
41 item.optimize();
42 }
43 }
44Self::Optional { format: _, item } => item.optimize(),
45 }
4647// Iterate over all optimization passes until no more changes are made.
48while passes.map(|pass| pass(self)).into_iter().any(identity) {}
49 }
5051const fn no_op() -> Self {
52Self::StringLiteral(String::new())
53 }
5455/// 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.
59fn merge_consecutive_literals(&mut self) -> bool {
60let Self::Compound(items) = selfelse {
61return false;
62 };
6364let mut something_was_changed = false;
65let mut idx = 1;
66while idx < items.len() {
67// Safety: `idx - 1` is not equal to `idx` and both are in-bounds.
68let pair = unsafe { items.get_disjoint_unchecked_mut([idx - 1, idx]) };
6970match 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)] => {
82let Self::StringLiteral(a) = item else {
83::core::panicking::panic("internal error: entered unreachable code")unreachable!()84 };
85let 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 }
99100something_was_changed101 }
102103/// When a compound item only contains a single item, it can be replaced with that item.
104fn unnest_trivial_compounds(&mut self) -> bool {
105if let Self::Compound(items) = self106 && items.len() == 1
107&& let Some(item) = items.pop()
108 {
109*self = item;
110true
111} else {
112false
113}
114 }
115116/// When a compound item contains another compound item, the latter can be inlined into the
117 /// former.
118fn unnest_nested_compounds(&mut self) -> bool {
119let Self::Compound(items) = selfelse {
120return false;
121 };
122123let mut idx = 0;
124let mut something_was_changed = false;
125while idx < items.len() {
126if let Self::Compound(inner_items) = &mut items[idx] {
127let 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 }
134135something_was_changed136 }
137138/// When a first item only contains a single item, it can be replaced with that item.
139fn unnest_first_only_one(&mut self) -> bool {
140if let Self::First(items) = self141 && items.len() == 1
142&& let Some(item) = items.pop()
143 {
144*self = item;
145true
146} else {
147false
148}
149 }
150151/// When a first item contains another first item, the latter can be inlined into the former.
152fn unnest_nested_first(&mut self) -> bool {
153let Self::First(items) = selfelse {
154return false;
155 };
156157let mut idx = 0;
158let mut something_was_changed = false;
159while idx < items.len() {
160if let Self::First(inner_items) = &mut items[idx] {
161let 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 }
168169something_was_changed170 }
171172/// 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).
176fn 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.
179if !truecfg!(feature = "formatting") || truecfg!(feature = "parsing") {
180return false;
181 }
182183let Self::Optional { format, item } = selfelse {
184return false;
185 };
186187let item = if *format {
188 mem::replace(item.as_mut(), Self::no_op())
189 } else {
190Self::no_op()
191 };
192193*self = item;
194true
195}
196197/// 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.
200fn 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.
203if !truecfg!(feature = "formatting") || truecfg!(feature = "parsing") {
204return false;
205 }
206207let Self::First(items) = selfelse {
208return false;
209 };
210211*self = items.remove(0);
212true
213}
214215fn 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.
218if !truecfg!(feature = "formatting") || truecfg!(feature = "parsing") {
219return false;
220 }
221222if let Self::Component(Component::End(_)) = self {
223*self = Self::no_op();
224true
225} else {
226false
227}
228 }
229230/// When a compound item contains an empty string literal, it can be removed as it has no
231 /// effect.
232fn compound_containing_empty_string(&mut self) -> bool {
233let Self::Compound(items) = selfelse {
234return false;
235 };
236237let mut idx = 0;
238let mut something_was_changed = false;
239while idx < items.len() {
240if 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 }
249250something_was_changed251 }
252}