github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/pkg/util/pretty/util.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package pretty 12 13 // This file contains utility and other non-standard functions not listed 14 // in the paper (see the package documentation). Utility functions are those 15 // that are generally useful or need to create certain structures (like union) 16 // in a correct way. 17 18 // Join joins Docs d with string s and Line. There is no space between each 19 // item in d and the subsequent instance of s. 20 func Join(s string, d ...Doc) Doc { 21 return JoinDoc(Concat(Text(s), Line), d...) 22 } 23 24 // JoinDoc joins Docs d with Doc s. 25 func JoinDoc(s Doc, d ...Doc) Doc { 26 switch len(d) { 27 case 0: 28 return Nil 29 case 1: 30 return d[0] 31 default: 32 return Fold(Concat, d[0], s, JoinDoc(s, d[1:]...)) 33 } 34 } 35 36 // JoinNestedRight nests nested with string s. 37 // Every item after the first is indented. 38 // For example: 39 // aaaa 40 // <sep> bbb 41 // 42 // bbb 43 // 44 // <sep> ccc 45 // 46 // ccc 47 func JoinNestedRight(sep Doc, nested ...Doc) Doc { 48 switch len(nested) { 49 case 0: 50 return Nil 51 case 1: 52 return nested[0] 53 default: 54 return Concat( 55 nested[0], 56 FoldMap(Concat, 57 func(a Doc) Doc { return Concat(Line, ConcatSpace(sep, NestT(Group(a)))) }, 58 nested[1:]...)) 59 } 60 } 61 62 // ConcatDoc concatenates two Docs with between. 63 func ConcatDoc(a, b, between Doc) Doc { 64 return simplifyNil(a, b, func(a, b Doc) Doc { 65 return Concat( 66 a, 67 Concat( 68 between, 69 b, 70 ), 71 ) 72 }) 73 } 74 75 // ConcatLine concatenates two Docs with a Line. 76 func ConcatLine(a, b Doc) Doc { 77 return ConcatDoc(a, b, Line) 78 } 79 80 // ConcatSpace concatenates two Docs with a space. 81 func ConcatSpace(a, b Doc) Doc { 82 return ConcatDoc(a, b, textSpace) 83 } 84 85 // Stack concats Docs with a Line between each. 86 func Stack(d ...Doc) Doc { 87 return Fold(ConcatLine, d...) 88 } 89 90 // Fillwords fills lines with as many docs as will fit joined with a space or 91 // line. 92 func Fillwords(d ...Doc) Doc { 93 u := &union{textSpace, line{}} 94 fill := func(a, b Doc) Doc { 95 return ConcatDoc(a, b, u) 96 } 97 return Fold(fill, d...) 98 } 99 100 // JoinGroupAligned nests joined d with divider under head. 101 func JoinGroupAligned(head, divider string, d ...Doc) Doc { 102 return AlignUnder(Text(head), Join(divider, d...)) 103 } 104 105 // NestUnder nests nested under head. 106 func NestUnder(head, nested Doc) Doc { 107 return Group(Concat( 108 head, 109 NestT(Concat( 110 Line, 111 Group(nested), 112 )), 113 )) 114 } 115 116 // AlignUnder aligns nested to the right of head, and, if 117 // this does not fit on the line, nests nested under head. 118 func AlignUnder(head, nested Doc) Doc { 119 g := Group(nested) 120 a := ConcatSpace(head, Align(g)) 121 b := Concat(head, NestT(Concat(Line, g))) 122 return Group(&union{a, b}) 123 } 124 125 // Fold applies f recursively to all Docs in d. 126 func Fold(f func(a, b Doc) Doc, d ...Doc) Doc { 127 switch len(d) { 128 case 0: 129 return Nil 130 case 1: 131 return d[0] 132 default: 133 return f(d[0], Fold(f, d[1:]...)) 134 } 135 } 136 137 // FoldMap applies f recursively to all Docs in d processed through g. 138 func FoldMap(f func(a, b Doc) Doc, g func(Doc) Doc, d ...Doc) Doc { 139 switch len(d) { 140 case 0: 141 return Nil 142 case 1: 143 return g(d[0]) 144 default: 145 return f(g(d[0]), FoldMap(f, g, d[1:]...)) 146 } 147 } 148 149 // BracketDoc is like Bracket except it accepts Docs instead of strings. 150 func BracketDoc(l, x, r Doc) Doc { 151 return Group(Fold(Concat, 152 l, 153 NestT(Concat(SoftBreak, x)), 154 SoftBreak, 155 r, 156 )) 157 } 158 159 // simplifyNil returns fn(a, b). If either Doc is Nil, the other Doc 160 // is returned without invoking fn. 161 func simplifyNil(a, b Doc, fn func(Doc, Doc) Doc) Doc { 162 if a == Nil { 163 return b 164 } 165 if b == Nil { 166 return a 167 } 168 return fn(a, b) 169 } 170 171 // JoinNestedOuter attempts to de-indent the items after the first so 172 // that the operator appears in the right margin. This replacement is 173 // only done if there are enough "simple spaces" (as per previous uses 174 // of Align) to de-indent. No attempt is made to de-indent hard tabs, 175 // otherwise alignment may break on output devices with a different 176 // physical tab width. docFn should be set to Text or Keyword and will be 177 // used when converting lbl to a Doc. 178 func JoinNestedOuter(lbl string, docFn func(string) Doc, d ...Doc) Doc { 179 sep := docFn(lbl) 180 return &snesting{ 181 f: func(k int16) Doc { 182 if k < int16(len(lbl)+1) { 183 // If there is not enough space, don't even try. Just use a 184 // regular nested section. 185 return JoinNestedRight(sep, d...) 186 } 187 // If there is enough space, on the other hand, we can de-indent 188 // every line after the first. 189 return Concat(d[0], 190 NestS(int16(-len(lbl)-1), 191 FoldMap( 192 Concat, 193 func(x Doc) Doc { 194 return Concat( 195 Line, 196 ConcatSpace(sep, Align(Group(x)))) 197 }, 198 d[1:]...))) 199 }, 200 } 201 } 202 203 // TableRow is the data for one row of a RLTable (see below). 204 type TableRow struct { 205 Label string 206 Doc Doc 207 } 208 209 // TableAlignment should be used as first argument to Table(). 210 type TableAlignment int 211 212 const ( 213 // TableNoAlign does not use alignment and instead uses NestUnder. 214 TableNoAlign TableAlignment = iota 215 // TableRightAlignFirstColumn right-aligns (left-pads) the first column. 216 TableRightAlignFirstColumn 217 // TableLeftAlignFirstColumn left-aligns (right-pads) the first column. 218 TableLeftAlignFirstColumn 219 ) 220 221 // Table defines a document that formats a list of pairs of items either: 222 // - as a 2-column table, with the two columns aligned for example: 223 // SELECT aaa 224 // bbb 225 // FROM ccc 226 // - as sections, for example: 227 // SELECT 228 // aaa 229 // bbb 230 // FROM 231 // ccc 232 // 233 // We restrict the left value in each list item to be a one-line string 234 // to make the width computation efficient. 235 // 236 // For convenience, the function also skips over rows with a nil 237 // pointer as doc. 238 // 239 // docFn should be set to Text or Keyword and will be used when converting 240 // TableRow label's to Docs. 241 func Table(alignment TableAlignment, docFn func(string) Doc, noNewLine bool, rows ...TableRow) Doc { 242 // Compute the nested formatting in "sections". It's simple. 243 // Note that we do not use NestUnder() because we are not grouping 244 // at this level (the group is done for the final form below). 245 items := makeTableNestedSections(docFn, rows, noNewLine) 246 nestedSections := Stack(items...) 247 248 finalDoc := nestedSections 249 250 if alignment != TableNoAlign { 251 items = makeAlignedTableItems(alignment == TableRightAlignFirstColumn /* leftPad */, docFn, rows, items) 252 alignedTable := Stack(items...) 253 finalDoc = &union{alignedTable, nestedSections} 254 } 255 256 return Group(finalDoc) 257 } 258 259 func computeLeftColumnWidth(rows []TableRow) int { 260 leftwidth := 0 261 for _, r := range rows { 262 if r.Doc == nil { 263 continue 264 } 265 if leftwidth < len(r.Label) { 266 leftwidth = len(r.Label) 267 } 268 } 269 return leftwidth 270 } 271 272 func makeAlignedTableItems( 273 leftPad bool, docFn func(string) Doc, rows []TableRow, items []Doc, 274 ) []Doc { 275 // We'll need the left column width below. 276 leftWidth := computeLeftColumnWidth(rows) 277 // Now convert the rows. 278 items = items[:0] 279 for _, r := range rows { 280 if r.Doc == nil || (r.Label == "" && r.Doc == Nil) { 281 continue 282 } 283 if r.Label != "" { 284 lbl := docFn(r.Label) 285 var pleft Doc 286 if leftPad { 287 pleft = Concat(pad{int16(leftWidth - len(r.Label))}, lbl) 288 } else { 289 pleft = Concat(lbl, pad{int16(leftWidth - len(r.Label))}) 290 } 291 d := simplifyNil(pleft, r.Doc, 292 func(a, b Doc) Doc { 293 return ConcatSpace(a, Align(Group(b))) 294 }) 295 items = append(items, d) 296 } else { 297 items = append(items, Concat(pad{int16(leftWidth + 1)}, Align(Group(r.Doc)))) 298 } 299 } 300 return items 301 } 302 303 func makeTableNestedSections(docFn func(string) Doc, rows []TableRow, noNewLine bool) []Doc { 304 items := make([]Doc, 0, len(rows)) 305 for _, r := range rows { 306 if r.Doc == nil || (r.Label == "" && r.Doc == Nil) { 307 continue 308 } 309 if r.Label != "" { 310 d := simplifyNil(docFn(r.Label), r.Doc, 311 func(a, b Doc) Doc { 312 if noNewLine { 313 return ConcatSpace(a, b) 314 } 315 return Concat(a, NestT(Concat(Line, Group(b)))) 316 }) 317 items = append(items, d) 318 } else { 319 items = append(items, Group(r.Doc)) 320 } 321 } 322 return items 323 }