1 module format.chunk; 2 3 enum ChunkKind { 4 Text, 5 Block, 6 } 7 8 enum Separator { 9 None, 10 Space, 11 NewLine, 12 TwoNewLines, 13 } 14 15 struct Chunk { 16 private: 17 import util.bitfields, std.typetuple; 18 alias FieldsTuple = TypeTuple!( 19 // sdfmt off 20 ChunkKind, "_kind", EnumSize!ChunkKind, 21 Separator, "_separator", EnumSize!Separator, 22 // Emit chunks one after the other, 23 // without adding extra padding or indentation. 24 bool, "_glued", 1, 25 // Consider this chunk as a continuation of the previous one. 26 // New lines won't be considered as split. 27 bool, "_continuation", 1, 28 // What is the base indentation level for this chunk. 29 // This becomes irrelevent when the chunk is glued. 30 uint, "_indentation", 10, 31 // This marks the boundary between unwrapped lines. 32 // Each unwrapped line can be formatted completely 33 // independently of other unwrapped lines. 34 bool, "_startsUnwrappedLine", 1, 35 // This marks the boundary between regions. 36 // The effect of formatting rtules are bounded by region 37 // so the writer can use this to detect redundant paths. 38 bool, "_startsRegion", 1, 39 // The length of the line in graphemes. 40 uint, "_length", 16, 41 // sdfmt on 42 ); 43 44 enum Pad = ulong.sizeof * 8 - SizeOfBitField!FieldsTuple; 45 46 import std.bitmanip; 47 mixin(bitfields!(FieldsTuple, ulong, "", Pad)); 48 49 import format.span; 50 Span _span = null; 51 52 union { 53 string _text; 54 Chunk[] _chunks; 55 } 56 57 public: 58 @property 59 ChunkKind kind() const { 60 return _kind; 61 } 62 63 @property 64 Separator separator() const { 65 return _separator; 66 } 67 68 @property 69 uint newLineCount() const { 70 return (separator >= Separator.NewLine) 71 + (separator == Separator.TwoNewLines); 72 } 73 74 @property 75 bool glued() const { 76 return _glued; 77 } 78 79 @property 80 bool continuation() const { 81 return _continuation; 82 } 83 84 bool canSplit() const { 85 return !glued && !continuation; 86 } 87 88 @property 89 bool startsUnwrappedLine() const { 90 return _startsUnwrappedLine; 91 } 92 93 @property 94 bool startsRegion() const { 95 return _startsRegion; 96 } 97 98 @property 99 uint indentation() const { 100 return _indentation; 101 } 102 103 @property 104 uint length() const { 105 return _length; 106 } 107 108 @property 109 inout(Span) span() inout { 110 return _span; 111 } 112 113 bool contains(const Span s) const { 114 return span.contains(s); 115 } 116 117 @property 118 bool empty() const { 119 return kind ? chunks.length == 0 : text.length == 0; 120 } 121 122 @property 123 ref inout(string) text() inout in { 124 assert(kind == ChunkKind.Text); 125 } do { 126 return _text; 127 } 128 129 @property 130 ref inout(Chunk[]) chunks() inout in { 131 assert(kind == ChunkKind.Block); 132 } do { 133 return _chunks; 134 } 135 136 string toString() const { 137 import std.conv; 138 return "Chunk(" ~ separator.to!string ~ ", " ~ Span.print(span) ~ ", " 139 ~ glued.to!string ~ ", " ~ continuation.to!string ~ ", " 140 ~ indentation.to!string ~ ", " ~ length.to!string ~ ", " 141 ~ (kind ? chunks.to!string : [text].to!string) ~ ")"; 142 } 143 } 144 145 struct Builder { 146 private: 147 Chunk chunk; 148 Chunk[] source; 149 150 Separator pendingSeparator = Separator.None; 151 uint indentation; 152 153 import format.span; 154 Span spanStack = null; 155 size_t spliceIndex = 0; 156 157 struct Fixup { 158 size_t index; 159 Span span; 160 void function(Span span, size_t i) fun; 161 162 void fix(const ref Chunk c, size_t pre, size_t post) { 163 fun(span, c.contains(span) ? pre : post); 164 } 165 } 166 167 Fixup[] fixups; 168 169 public: 170 Chunk[] build() { 171 split(); 172 173 size_t start = 0; 174 size_t fi = 0; 175 176 // Make sure we consumed all fixups by the end of the process. 177 scope(success) { 178 assert(fi == fixups.length); 179 } 180 181 Span previousTop = null; 182 183 foreach (i, ref c; source) { 184 auto top = c.span.getTop(); 185 186 // If we have a new set of spans, then we have a new region. 187 c._startsRegion = top is null || top !is previousTop; 188 189 // Bucket brigade. 190 previousTop = top; 191 192 size_t indexinLine = i - start; 193 194 scope(success) { 195 // Run fixups that the parser may have registered. 196 while (fi < fixups.length && fixups[fi].index == i) { 197 fixups[fi++].fix(c, i - start, indexinLine); 198 } 199 } 200 201 // If this is not a new region, this is not an unwrapped line break. 202 if (!c.startsRegion) { 203 continue; 204 } 205 206 // If this is glued, this is not an unwrapped line break. 207 if (c.glued) { 208 continue; 209 } 210 211 if (top !is null) { 212 continue; 213 } 214 215 // If this is not a line break, this is not an unwrapped line break. 216 if (i > 0 && c.newLineCount() == 0) { 217 continue; 218 } 219 220 // This is a line break with no span in common. 221 c._startsUnwrappedLine = true; 222 start = i; 223 } 224 225 return source; 226 } 227 228 /** 229 * Write into the next chunk. 230 */ 231 void write(string s) { 232 emitPendingSeparator(); 233 234 import std.stdio; 235 // writeln("write: ", [s]); 236 chunk.text ~= s; 237 } 238 239 void space() { 240 import std.stdio; 241 // writeln("space!"); 242 setSeparator(Separator.Space); 243 } 244 245 void newline(int nLines = 1) { 246 import std.stdio; 247 // writeln("newline ", nLines); 248 setSeparator(nLines > 1 ? Separator.TwoNewLines : Separator.NewLine); 249 } 250 251 void clearSeparator() { 252 import std.stdio; 253 // writeln("clearSeparator!"); 254 pendingSeparator = Separator.None; 255 } 256 257 void split(bool glued = false, bool continuation = false) { 258 import std.stdio; 259 260 // writeln("split!", glued ? " glued" : "", continuation ? " continuation" : ""); 261 262 scope(success) { 263 chunk._span = spanStack; 264 chunk._glued = glued; 265 chunk._continuation = continuation; 266 267 emitPendingSeparator(); 268 } 269 270 bool isText = chunk.kind == ChunkKind.Text; 271 if (isText && !glued) { 272 uint nlCount = 0; 273 274 size_t last = chunk.text.length; 275 while (last > 0) { 276 char lastChar = chunk.text[last - 1]; 277 278 import std.ascii; 279 if (!isWhite(lastChar)) { 280 break; 281 } 282 283 last--; 284 if (lastChar == ' ') { 285 space(); 286 } 287 288 if (lastChar == '\n') { 289 nlCount++; 290 } 291 } 292 293 if (nlCount) { 294 newline(nlCount); 295 } 296 297 chunk.text = chunk.text[0 .. last]; 298 } 299 300 // There is nothing to flush. 301 if (chunk.empty) { 302 if (chunk.newLineCount == 0) { 303 // Fuse the chunk if it doesn't start a new line. 304 return; 305 } 306 307 if (!continuation || !chunk.continuation) { 308 // Only fuse if one of the chunks is not a continuation. 309 // FIXME: This overfuse in the following case: 310 // newline, continuation = false, test = "" 311 // split(continuation : true) 312 // Unfortunately, this erroneous behavior is relied upon, 313 // so it'll have to stay for now. 314 return; 315 } 316 } 317 318 if (isText) { 319 import std.uni, std.range; 320 chunk._length = cast(uint) chunk.text.byGrapheme.walkLength(); 321 } 322 323 source ~= chunk; 324 chunk = Chunk(); 325 } 326 327 /** 328 * Indentation and alignement. 329 */ 330 auto indent(uint level = 1) { 331 emitPendingSeparator(); 332 333 static struct Guard { 334 ~this() { 335 builder.indentation = oldLevel; 336 builder.emitPendingSeparator(); 337 } 338 339 private: 340 Builder* builder; 341 uint oldLevel; 342 } 343 344 uint oldLevel = indentation; 345 indentation += level; 346 347 return Guard(&this, oldLevel); 348 } 349 350 auto unindent(uint level = 1) { 351 import std.algorithm; 352 level = min(level, indentation); 353 return indent(-level); 354 } 355 356 /** 357 * Span management. 358 */ 359 auto span(S = Span, T...)(T args) { 360 emitPendingSeparator(); 361 362 static struct Guard { 363 this(Builder* builder, S span) { 364 this.builder = builder; 365 this.span = span; 366 this.spliceIndex = builder.source.length; 367 builder.spanStack = span; 368 } 369 370 ~this() { 371 assert(builder.spanStack is span); 372 builder.spanStack = span.parent; 373 builder.spliceIndex = spliceIndex; 374 375 if (builder.chunk.empty) { 376 // Make sure we get the proper span. 377 builder.chunk._span = span.parent; 378 } 379 } 380 381 void registerFix(void function(S s, size_t i) fix) { 382 builder.fixups ~= Fixup( 383 builder.source.length, span, 384 // Fixup is not templated, so we need to give it the type 385 // it expects. The compiler cannot verify this for us, but we 386 // know it is always called with the supplied span as argument, 387 // which we know have the right type. 388 cast(void function(Span span, ulong i)) fix); 389 } 390 391 private: 392 Builder* builder; 393 S span; 394 size_t spliceIndex; 395 } 396 397 return Guard(&this, new S(spanStack, args)); 398 } 399 400 auto virtualSpan() { 401 emitPendingSeparator(); 402 403 static struct Guard { 404 this(Builder* builder) { 405 this.builder = builder; 406 this.spliceIndex = builder.source.length; 407 } 408 409 ~this() { 410 builder.spliceIndex = spliceIndex; 411 } 412 413 private: 414 Builder* builder; 415 size_t spliceIndex; 416 } 417 418 return Guard(&this); 419 } 420 421 auto spliceSpan(S = Span, T...)(T args) { 422 Span parent = spanStack; 423 auto guard = span!S(args); 424 425 Span span = guard.span; 426 Span previous = parent; 427 428 import std.range; 429 foreach (ref c; source[spliceIndex .. $].chain(only(chunk)).retro()) { 430 Span current = c.span; 431 scope(success) { 432 previous = current; 433 } 434 435 if (current is parent) { 436 c._span = span; 437 continue; 438 } 439 440 if (current is previous) { 441 // We already handled this. 442 continue; 443 } 444 445 Span insert = current; 446 while (insert !is null && insert.parent !is parent) { 447 insert = insert.parent; 448 } 449 450 if (insert is null) { 451 // We reached the end of the parent span. 452 break; 453 } 454 455 if (insert !is span) { 456 insert.parent = span; 457 } 458 } 459 460 return guard; 461 } 462 463 /** 464 * Block management. 465 */ 466 auto block() { 467 // We delegate indentation to the block itself. 468 emitPendingSeparator(); 469 470 // We delegate indentation to the block itself. 471 split(true, true); 472 473 static struct Guard { 474 ~this() { 475 auto chunk = outerBuilder.chunk; 476 chunk._kind = ChunkKind.Block; 477 chunk.chunks = builder.build(); 478 479 // Restore the outer builder. 480 *builder = outerBuilder; 481 builder.chunk = chunk; 482 483 // Do not use the regular line splitter for blocks. 484 builder.newline(1); 485 builder.split(false, true); 486 } 487 488 private: 489 Builder* builder; 490 Builder outerBuilder; 491 } 492 493 auto guard = Guard(&this, this); 494 495 // Get ready to build the block. 496 this = Builder(); 497 498 return guard; 499 } 500 501 private: 502 void setSeparator(Separator s) { 503 import std.algorithm; 504 pendingSeparator = max(pendingSeparator, s); 505 } 506 507 void emitPendingSeparator() { 508 scope(success) { 509 import std.algorithm; 510 chunk._separator = max(chunk.separator, pendingSeparator); 511 pendingSeparator = Separator.None; 512 } 513 514 if (chunk.empty) { 515 // Indentation is part of the separator. 516 chunk._indentation = indentation; 517 return; 518 } 519 520 final switch (pendingSeparator) with (Separator) { 521 case None: 522 // nothing to do. 523 break; 524 525 case Space: 526 chunk.text ~= ' '; 527 pendingSeparator = None; 528 break; 529 530 case NewLine, TwoNewLines: 531 split(); 532 break; 533 } 534 } 535 }