1use std::fmt;
13
14use mz_ore::str::Indent;
15
16use crate::explain::{
17 CompactScalarSeq, CompactScalars, ExprHumanizer, IndexUsageType, Indices, ScalarOps,
18 UnsupportedFormat, UsedIndexes,
19};
20
21pub trait DisplayText<C = ()>
24where
25 Self: Sized,
26{
27 fn fmt_text(&self, f: &mut fmt::Formatter<'_>, ctx: &mut C) -> fmt::Result;
28}
29
30impl<T> DisplayText for &T
31where
32 T: DisplayText,
33{
34 fn fmt_text(&self, f: &mut fmt::Formatter<'_>, ctx: &mut ()) -> fmt::Result {
35 (*self).fmt_text(f, ctx)
36 }
37}
38
39impl<A, C> DisplayText<C> for Box<A>
40where
41 A: DisplayText<C>,
42{
43 fn fmt_text(&self, f: &mut fmt::Formatter<'_>, ctx: &mut C) -> fmt::Result {
44 self.as_ref().fmt_text(f, ctx)
45 }
46}
47
48impl<A, C> DisplayText<C> for Option<A>
49where
50 A: DisplayText<C>,
51{
52 fn fmt_text(&self, f: &mut fmt::Formatter<'_>, ctx: &mut C) -> fmt::Result {
53 if let Some(val) = self {
54 val.fmt_text(f, ctx)
55 } else {
56 fmt::Result::Ok(())
57 }
58 }
59}
60
61impl DisplayText for UnsupportedFormat {
62 fn fmt_text(&self, _f: &mut fmt::Formatter<'_>, _ctx: &mut ()) -> fmt::Result {
63 unreachable!()
64 }
65}
66
67impl<'a, C> DisplayText<C> for UsedIndexes
68where
69 C: AsMut<Indent> + AsRef<&'a dyn ExprHumanizer>,
70{
71 fn fmt_text(&self, f: &mut fmt::Formatter<'_>, ctx: &mut C) -> fmt::Result {
72 writeln!(f, "{}Used Indexes:", ctx.as_mut())?;
73 *ctx.as_mut() += 1;
74 for (id, usage_types) in &self.0 {
75 let usage_types = IndexUsageType::display_vec(usage_types);
76 if let Some(name) = ctx.as_ref().humanize_id(*id) {
77 writeln!(f, "{}- {} ({})", ctx.as_mut(), name, usage_types)?;
78 } else {
79 writeln!(f, "{}- [DELETED INDEX] ({})", ctx.as_mut(), usage_types)?;
80 }
81 }
82 *ctx.as_mut() -= 1;
83 Ok(())
84 }
85}
86
87impl<'a> fmt::Display for Indices<'a> {
88 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
89 let mut is_first = true;
90 let mut slice = self.0;
91 while !slice.is_empty() {
92 if !is_first {
93 write!(f, ", ")?;
94 }
95 is_first = false;
96 let lead = &slice[0];
97 if slice.len() > 2 && slice[1] == lead + 1 && slice[2] == lead + 2 {
98 let mut last = 3;
99 while slice.get(last) == Some(&(lead + last)) {
100 last += 1;
101 }
102 write!(f, "#{}..=#{}", lead, lead + last - 1)?;
103 slice = &slice[last..];
104 } else {
105 write!(f, "#{}", slice[0])?;
106 slice = &slice[1..];
107 }
108 }
109 Ok(())
110 }
111}
112
113impl<'a, T> std::fmt::Display for CompactScalarSeq<'a, T>
114where
115 T: ScalarOps + fmt::Display,
116{
117 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
118 let mut is_first = true;
119 let mut slice = self.0;
120 while !slice.is_empty() {
121 if !is_first {
122 write!(f, ", ")?;
123 }
124 is_first = false;
125 if let Some(lead) = slice[0].match_col_ref() {
126 if slice.len() > 2 && slice[1].references(lead + 1) && slice[2].references(lead + 2)
127 {
128 let mut last = 3;
129 while slice
130 .get(last)
131 .map(|expr| expr.references(lead + last))
132 .unwrap_or(false)
133 {
134 last += 1;
135 }
136 slice[0].fmt(f)?;
137 write!(f, "..=")?;
138 slice[last - 1].fmt(f)?;
139 slice = &slice[last..];
140 } else {
141 slice[0].fmt(f)?;
142 slice = &slice[1..];
143 }
144 } else {
145 slice[0].fmt(f)?;
146 slice = &slice[1..];
147 }
148 }
149 Ok(())
150 }
151}
152
153impl<T, I> fmt::Display for CompactScalars<T, I>
154where
155 T: ScalarOps + fmt::Display,
156 I: Iterator<Item = T> + Clone,
157{
158 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
159 enum State<T> {
160 Start,
161 FoundOne(T, usize), FoundTwo(T, T, usize), FoundRun(T, T, usize), }
165
166 let mut state = State::Start;
167
168 let mut is_first = true;
169 let mut prefix = || {
170 if std::mem::replace(&mut is_first, false) {
171 ""
172 } else {
173 ", "
174 }
175 };
176
177 for n in self.0.clone() {
178 state = match state {
179 State::Start => match n.match_col_ref() {
180 Some(n_col) => {
181 State::FoundOne(n, n_col) }
183 None => {
184 write!(f, "{}{n}", prefix())?;
185 State::Start }
187 },
188 State::FoundOne(x, x_col) => match n.match_col_ref() {
189 Some(n_col) => {
190 if x_col + 1 == n_col {
191 State::FoundTwo(x, n, n_col) } else {
193 write!(f, "{}{x}", prefix())?;
194 State::FoundOne(n, n_col) }
196 }
197 None => {
198 write!(f, "{}{x}, {n}", prefix())?;
199 State::Start }
201 },
202 State::FoundTwo(x, y, y_col) => match n.match_col_ref() {
203 Some(n_col) => {
204 if y_col + 1 == n_col {
205 State::FoundRun(x, n, n_col) } else {
207 write!(f, "{}{x}, {y}", prefix())?;
208 State::FoundOne(n, n_col) }
210 }
211 None => {
212 write!(f, "{}{x}, {y}, {n}", prefix())?;
213 State::Start }
215 },
216 State::FoundRun(x, y, y_col) => match n.match_col_ref() {
217 Some(n_col) => {
218 if y_col + 1 == n_col {
219 State::FoundRun(x, n, n_col) } else {
221 write!(f, "{}{x}..={y}", prefix())?;
222 State::FoundOne(n, n_col) }
224 }
225 None => {
226 write!(f, "{}{x}..={y}, {n}", prefix())?;
227 State::Start }
229 },
230 };
231 }
232
233 match state {
234 State::Start => {
235 }
237 State::FoundOne(x, _) => {
238 write!(f, "{}{x}", prefix())?;
239 }
240 State::FoundTwo(x, y, _) => {
241 write!(f, "{}{x}, {y}", prefix())?;
242 }
243 State::FoundRun(x, y, _) => {
244 write!(f, "{}{x}..={y}", prefix())?;
245 }
246 }
247
248 Ok(())
249 }
250}
251
252pub fn text_string<T: DisplayText>(t: &T) -> String {
258 struct TextString<'a, T>(&'a T);
259
260 impl<'a, F: DisplayText> fmt::Display for TextString<'a, F> {
261 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262 self.0.fmt_text(f, &mut ())
263 }
264 }
265
266 TextString::<'_>(t).to_string()
267}
268
269pub fn text_string_at<'a, T: DisplayText<C>, C, F: Fn() -> C>(t: &'a T, f: F) -> String {
275 struct TextStringAt<'a, T, C, F: Fn() -> C> {
276 t: &'a T,
277 f: F,
278 }
279
280 impl<T: DisplayText<C>, C, F: Fn() -> C> DisplayText<()> for TextStringAt<'_, T, C, F> {
281 fn fmt_text(&self, f: &mut fmt::Formatter<'_>, _ctx: &mut ()) -> fmt::Result {
282 let mut ctx = (self.f)();
283 self.t.fmt_text(f, &mut ctx)
284 }
285 }
286
287 text_string(&TextStringAt { t, f })
288}