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.
1011use std::mem;
1213use super::{Component, OwnedFormatItem, OwnedFormatItemInner};
1415impl OwnedFormatItem {
16pub(crate) fn optimize(&mut self) {
17self.inner.optimize();
18 }
19}
2021impl OwnedFormatItemInner {
22pub(crate) fn optimize(&mut self) {
23// Walk the tree and optimize all children.
24match self {
25Self::Literal(_) | Self::StringLiteral(_) | Self::Component(_) => {}
26Self::Compound(items) | Self::First(items) => {
27for item in items {
28 item.optimize();
29 }
30 }
31Self::Optional { format: _, item } => item.optimize(),
32 }
3334// 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.
38let passes = [
39// type-changing passes
40Self::only_formatting_uplift_optional,
41Self::only_formatting_uplift_first,
42Self::only_formatting_eliminate_end,
43// structural passes: inline nested containers, remove no-op children
44Self::unnest_nested_compounds,
45Self::unnest_nested_first,
46Self::compound_containing_empty_string,
47// consuming passes: merge siblings, unwrap trivial wrappers
48Self::merge_consecutive_literals,
49Self::unnest_trivial_compounds,
50Self::unnest_first_only_one,
51 ];
52for pass in passes {
53 pass(self);
54 }
55 }
5657const fn no_op() -> Self {
58Self::StringLiteral(String::new())
59 }
6061/// 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.
65fn merge_consecutive_literals(&mut self) -> bool {
66let Self::Compound(items) = selfelse {
67return false;
68 };
6970let mut something_was_changed = false;
71let mut idx = 1;
72while idx < items.len() {
73// Safety: `idx - 1` is not equal to `idx` and both are in-bounds.
74let pair = unsafe { items.get_disjoint_unchecked_mut([idx - 1, idx]) };
7576match 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)] => {
88let Self::StringLiteral(a) = item else {
89::core::panicking::panic("internal error: entered unreachable code")unreachable!()90 };
91let 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 }
105106something_was_changed107 }
108109/// When a compound item only contains a single item, it can be replaced with that item.
110fn unnest_trivial_compounds(&mut self) -> bool {
111if let Self::Compound(items) = self112 && items.len() == 1
113&& let Some(item) = items.pop()
114 {
115*self = item;
116true
117} else {
118false
119}
120 }
121122/// When a compound item contains another compound item, the latter can be inlined into the
123 /// former.
124fn unnest_nested_compounds(&mut self) -> bool {
125let Self::Compound(items) = selfelse {
126return false;
127 };
128129let mut idx = 0;
130let mut something_was_changed = false;
131while idx < items.len() {
132if let Self::Compound(inner_items) = &mut items[idx] {
133let 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 }
140141something_was_changed142 }
143144/// When a first item only contains a single item, it can be replaced with that item.
145fn unnest_first_only_one(&mut self) -> bool {
146if let Self::First(items) = self147 && items.len() == 1
148&& let Some(item) = items.pop()
149 {
150*self = item;
151true
152} else {
153false
154}
155 }
156157/// When a first item contains another first item, the latter can be inlined into the former.
158fn unnest_nested_first(&mut self) -> bool {
159let Self::First(items) = selfelse {
160return false;
161 };
162163let mut idx = 0;
164let mut something_was_changed = false;
165while idx < items.len() {
166if let Self::First(inner_items) = &mut items[idx] {
167let 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 }
174175something_was_changed176 }
177178/// 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).
182fn 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.
185if !truecfg!(feature = "formatting") || truecfg!(feature = "parsing") {
186return false;
187 }
188189let Self::Optional { format, item } = selfelse {
190return false;
191 };
192193let item = if *format {
194 mem::replace(item.as_mut(), Self::no_op())
195 } else {
196Self::no_op()
197 };
198199*self = item;
200true
201}
202203/// 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.
206fn 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.
209if !truecfg!(feature = "formatting") || truecfg!(feature = "parsing") {
210return false;
211 }
212213let Self::First(items) = selfelse {
214return false;
215 };
216217*self = items.remove(0);
218true
219}
220221fn 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.
224if !truecfg!(feature = "formatting") || truecfg!(feature = "parsing") {
225return false;
226 }
227228if let Self::Component(Component::End(_)) = self {
229*self = Self::no_op();
230true
231} else {
232false
233}
234 }
235236/// When a compound item contains an empty string literal, it can be removed as it has no
237 /// effect.
238fn compound_containing_empty_string(&mut self) -> bool {
239let Self::Compound(items) = selfelse {
240return false;
241 };
242243let mut idx = 0;
244let mut something_was_changed = false;
245while idx < items.len() {
246if 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 }
255256something_was_changed257 }
258}