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 }