1 /** Backwards-compatible extensions of `std.stdio.{f}write{ln}` to `p{f}write{ln}`.
2  *
3  * Test: dmd -version=show -preview=dip1000 -preview=in -vcolumns -d -I.. -i -debug -g -checkaction=context -allinst -unittest -main -run stdio.d
4  *
5  * See_Also: `core.internal.dassert`
6  *
7  * NOTE: Replacing calls to overloaded `fpwrite1` with non-overloaded versions
8  * such as `fpwrite1_char`, `fpwrite1_string` reduces compilation memory usage.
9  *
10  * TODO: Cast to `arg const` for `T` being `struct` and `class` with `const toString` to avoid template-bloat
11  *
12  * TODO: Use setlocale to enable correct printing {w|d}char([])
13  * #include <wchar.h>
14  * #include <locale.h>
15  * #include <stdio.h>
16  *
17  * int main() {
18  *     // Set the locale to the user default, which should include Unicode support
19  *     setlocale(LC_ALL, "");
20  *
21  *     wchar_t letter1 = L'å';
22  *     wchar_t letter2 = L'ä';
23  *     wchar_t letter3 = L'ö';
24  *
25  *     wprintf(L"%lc %lc %lc\n", letter1, letter2, letter3);
26  *
27  *     return 0;
28  * }
29  *
30  * In D you use:
31  *
32  * import core.stdc.locale : setlocale, LC_ALL;
33  * () @trusted { setlocale(LC_ALL, ""); }();
34  *
35  * TODO: Perhaps make public (top) functions throw static exceptions keeping them `@nogc`.
36  */
37 module nxt.stdio;
38 
39 import core.stdc.stdio : stdout, fputc, fprintf, FILE, EOF, c_fwrite = fwrite;
40 import core.stdc.wchar_ : fputwc, fwprintf;
41 import nxt.visiting : Addrs = Addresses;
42 
43 // version = show;
44 
45 @safe:
46 
47 /++ Writing/Printing format. +/
48 @safe struct Format {
49 @safe pure nothrow @nogc:
50 	static Format plain() {
51 		typeof(return) result;
52 		result.showClassValues = true;
53 		result.showPointerValues = true;
54 		result.showEnumeratorEnumType = true;
55 		result.showEnumatorValues = true;
56 		result.useFonts = true;
57 		return result;
58 	}
59 	static Format pretty() {
60 		typeof(return) result = Format.plain;
61 		result.showFieldNames = true;
62 		result.useFonts = true;
63 		return result;
64 	}
65 	static Format fancy() {
66 		typeof(return) result = Format.pretty;
67 		result.multiLine = true;
68 		return result;
69 	}
70 	static Format debugging() {
71 		typeof(return) result = Format.fancy;
72 		result.showVoidArrayValues = true;
73 		result.dynamicArrayLengthMax = 8;
74 		result.multiLine = true;
75 		return result;
76 	}
77 	static Format everything() {
78 		typeof(return) result = Format.fancy;
79 		result.multiLine = true;
80 		return result;
81 	}
82 
83 	size_t level = 0; ///< Level of (aggregate) nesting starting at 0.
84 	string indentation = "\t"; ///< Indentation.
85 	char arrayPrefix = '['; ///< (Associative) Array prefix.
86 	char arraySuffix = ']'; ///< (Associative) Array suffix.
87 	char aggregatePrefix = '('; ///< Array fields prefix.
88 	char aggregateSuffix = ')'; ///< Array fields suffix.
89 	static immutable arrayElementSeparator = ", "; ///< Array element separator.
90 	static immutable aggregateFieldSeparator = ", "; ///< Aggregate field separator.
91 	char backReferencePrefix = '#'; ///< Backward reference prefix.
92 	// TODO: Use bitfields
93 	bool quoteChars; ///< Wrap characters {char|wchar|dchar} in ASCII single-quote character '\''.
94 	bool quoteStrings; ///< Wrap strings {string|wstring|dstring} in ASCII single-quote character '"'.
95 	bool showClassValues; ///< Show fields of non-null classes (instead of just pointer).
96 	bool showPointerValues; ///< Show values of non-null pointers (instead of just pointer).
97 	bool showEnumeratorEnumType; ///< Show enumerators as `EnumType.enumeratorValue` instead of `enumeratorValue`.
98 	bool showEnumatorValues; ///< Show values of enumerators instead of their names.
99 	bool showFieldNames = false; ///< Show names of fields.
100 	bool showFieldTypes = false; ///< Show types of values.
101 	bool showVoidArrayValues = false; ///< Show values of void arrays as ubytes instead of `[?]`.
102 	bool multiLine = false; ///< Span multiple lines using `indent`.
103 	bool useFonts; ///< Use different fonts for different types. TODO: Use nxt.ansi_escape
104 	size_t dynamicArrayLengthMax = size_t.max; ///< Limit length of dynamic arrays to this value when printing.
105 }
106 
107 ///
108 @safe pure unittest {
109 	assert(Format.plain != Format.pretty);
110 	assert(Format.pretty != Format.fancy);
111 }
112 
113 /** Pretty-formatted `fwrite(sm, ...)`. */
114 void fpwrite(Args...)(scope FILE* sm, in Format fmt, in Args args) {
115 	// pragma(msg, __FILE__, "(", __LINE__, ",1): Debug: ", Args);
116 	scope Addrs addrs;
117 	foreach (ref arg; args) {
118 		static if (is(typeof(arg) == enum))
119 			sm.fpwrite1_enum(arg, fmt, addrs);
120 		else
121 			sm.fpwrite1(arg, fmt, addrs);
122 	}
123 }
124 
125 /** Pretty-formatted `fwrite(stderr, ...)`. */
126 void epwrite(Args...)(in Format fmt, in Args args)
127 	=> fpwrite(stderr, fmt, args);
128 
129 /** Alternative to `std.stdio.fwrite`. */
130 void fwrite(Args...)(scope FILE* sm, in Args args)
131 	=> sm.fpwrite!(Args)(Format.init, args);
132 
133 /** Alternative to `std.stdio.write(stdout)`. */
134 void write(Args...)(in Args args)
135 	=> stdout.fwrite(args);
136 
137 /** Alternative to `std.stdio.write(stderr)`. */
138 void ewrite(Args...)(in Args args)
139 	=> stderr.fwrite(args);
140 
141 /** Pretty-formatted `fwriteln`. */
142 void fpwriteln(Args...)(scope FILE* sm, in Format fmt, in Args args) {
143 	sm.fpwrite!(Args)(fmt, args);
144 	const st = sm.fpwrite1_char('\n');
145 	// sm.fflush();
146 }
147 
148 /** Pretty-formatted `writeln`. */
149 void pwriteln(Args...)(in Format fmt, in Args args)
150 	=> stdout.fpwriteln(fmt, args);
151 /** Pretty-formatted `stderr.writeln`. */
152 void epwriteln(Args...)(in Format fmt, in Args args)
153 	=> stderr.fpwriteln(fmt, args);
154 
155 /** Alternative to `std.stdio.fwriteln`. */
156 void fwriteln(Args...)(scope FILE* sm, in Args args) {
157 	sm.fwrite(args);
158 	const st = sm.fpwrite1_char('\n');
159 	// sm.fflush();
160 }
161 
162 /** Alternative to `std.stdio.writeln`. */
163 void writeln(Args...)(in Args args)
164 	=> stdout.fwriteln(args);
165 
166 private:
167 
168 /** Pretty-formatted write single argument `arg` to `sm`. */
169 void fpwrite1(T)(scope FILE* sm, in T arg, in Format fmt, scope ref Addrs addrs) {
170 	// pragma(msg, __FILE__, "(", __LINE__, ",1): Debug: ", T);
171 	static if (is(T == enum)) {
172 		sm.fpwrite1_enum(arg, fmt, addrs);
173 	} else static if (__traits(hasMember, T, "toString") && is(typeof(arg.toString) : string)) {
174 		string str;
175 		() @trusted {
176 			/+ avoid scope compilation errors such as:
177 			   scope variable `arg` calling non-scope member function `JSONValue.toString()`
178 			 +/
179 			str = arg.toString;
180 		}();
181 		const int st = sm.fpwrite1_string(str, fmt.quoteStrings);
182 		return; // TODO: forward `st`
183 	} else static if (is(T == enum)) {
184 		static assert(0, "TODO: Branch on `fmt.showEnumatorValues`");
185 	} else static if (is(T : __vector(U[N]), U, size_t N)) /+ must come before `__traits(isArithmetic, T)` below because `is(__traits(isArithmetic, float4)` holds: +/ {
186 		const st = sm.fpwrite1ArrayValueExceptString(arg, fmt, addrs, false);
187 	} else static if (is(immutable T == immutable bool)) {
188 		const st = sm.fpwrite1_string(arg ? "true" : "false");
189 	} else static if (__traits(isArithmetic, T)) {
190 		static if (is(immutable T == immutable char)) {
191 			if (fmt.quoteChars) { const st1 = fputc('\'', sm); }
192 			const st = fputc(arg, sm);
193 			if (fmt.quoteChars) { const st2 = fputc('\'', sm); }
194 		} else static if (is(immutable T == immutable wchar) || is(immutable T == immutable dchar)) {
195 			if (fmt.quoteChars) { const st1 = fputc('\'', sm); }
196 			const st = fputwc(arg, sm);
197 			if (fmt.quoteChars) { const st2 = fputc('\'', sm); }
198 		} else {
199 			// See: `getPrintfFormat` in "core/internal/dassert.d"
200 			static      if (is(immutable T == immutable byte))
201 				immutable pff = "%hhd";
202 			else static if (is(immutable T == immutable ubyte))
203 				immutable pff = "%hhu";
204 			else static if (is(immutable T == immutable short))
205 				immutable pff = "%hd";
206 			else static if (is(immutable T == immutable ushort))
207 				immutable pff = "%hu";
208 			else static if (is(immutable T == immutable int))
209 				immutable pff = "%d";
210 			else static if (is(immutable T == immutable uint))
211 				immutable pff = "%u";
212 			else static if (is(immutable T == immutable long))
213 				immutable pff = "%lld";
214 			else static if (is(immutable T == immutable ulong))
215 				immutable pff = "%llu";
216 			else static if (is(immutable T == immutable float))
217 				immutable pff = "%g"; // or %e %g
218 			else static if (is(immutable T == immutable double))
219 				immutable pff = "%lg"; // or %le %lg
220 			else static if (is(immutable T == immutable real))
221 				immutable pff = "%Lg"; // or %Le %Lg
222 			else
223 				static assert(0, "TODO: Handle argument of type " ~ T.stringof);
224 			() @trusted { const st = sm.fprintf(pff.ptr, arg);} ();
225 		}
226 	} else static if (is(T : U[N], U, size_t N)) { // isStaticArray
227 		static if (is(U == char) || is(U == wchar) || is(U == dchar)) { // `isSomeChar`
228 			if (fmt.quoteStrings) { const st1 = sm.fpwrite1_char('"'); }
229 			const stm = sm.fpwrite1(arg, fmt);
230 			if (fmt.quoteStrings) { const st2 = sm.fpwrite1_char('"'); }
231 		} else {
232 			const st = sm.fpwrite1ArrayValueExceptString(arg[]/+ `arg[]` avoids template bloat +/, fmt, addrs, false);
233 		}
234 	} else static if (is(T : const(U)[], U)) { // isDynamicArray
235 		static if (is(U == char) || is(U == wchar) || is(U == dchar)) { // `isSomeChar`
236 			if (fmt.quoteStrings) { const st1 = sm.fpwrite1_char('"'); }
237 			const stm = sm.fpwrite1(arg, fmt);
238 			if (fmt.quoteStrings) { const st2 = sm.fpwrite1_char('"'); }
239 		} else {
240 			import std.algorithm.comparison : min;
241 			const truncated = arg.length > fmt.dynamicArrayLengthMax;
242 			const st = sm.fpwrite1ArrayValueExceptString(arg[0 .. min(arg.length, fmt.dynamicArrayLengthMax)], fmt, addrs, truncated);
243 		}
244 	} else static if (is(typeof(T.init.byKeyValue))) { // isAssociativeArray || isMap
245 		const st1 = sm.fpwrite1_char(fmt.arrayPrefix);
246 		size_t i;
247 		Format keyFmt = fmt; // key format
248 		Format valFmt = fmt; // value format
249 		alias Key = typeof(T.init.keys[0]);
250 		alias Val = typeof(T.init.values[0]);
251 		static if (!is(Key == struct) && !is(Key == class))
252 			keyFmt.indentation = null;
253 		static if (!is(Val == struct) && !is(Val == class))
254 			valFmt.indentation = null;
255 		foreach (ref kv; arg.byKeyValue) {
256 			if (i)
257 				const st1_ = sm.fpwrite1_string(fmt.arrayElementSeparator);
258 			sm.fpwrite1(kv.key, keyFmt, addrs);
259 			const st = sm.fpwrite1_string(": ");
260 			sm.fpwrite1(kv.value, valFmt, addrs);
261 			i += 1;
262 		}
263 		const st2 = sm.fpwrite1_char(fmt.arraySuffix);
264 	} else static if (is(T == struct)) {
265 		sm.fpwriteAggregate(arg, fmt, addrs);
266     } else static if (is(T == class) || is(T == const(U)*, U)) { // isAddress
267         const bool isNull = arg is null;
268 		if (isNull) {
269 			const st = sm.fpwrite1_string("null");
270 			return;
271 		}
272 		void* addr;
273 		() @trusted { addr = cast(void*)arg; }();
274 		() @trusted { const st = sm.fprintf("%lX", cast(size_t)addr, arg);} ();
275 		import nxt.algorithm.searching : indexOf;
276 		const ix = addrs[].indexOf(addr);
277 		if (ix != -1) { // `addr` already printed
278 			const st1 = sm.fpwrite1_char(fmt.backReferencePrefix);
279 			sm.fpwrite1(ix, fmt, addrs);
280 			return;
281 		}
282 		() @trusted { addrs ~= addr; }(); // `addrs`.lifetime <= `arg`.lifetime
283         static if (is(T == class)) {
284 			if (fmt.showClassValues) {
285 				const st = sm.fpwrite1_string(" . ");
286 				sm.fpwriteAggregate(arg, fmt, addrs);
287 			}
288         } else {
289 			if (fmt.showPointerValues) {
290 				static if (is(typeof(*arg))) {
291 					const st = sm.fpwrite1_string(" -> ");
292 					() @trusted {
293 						sm.fpwrite1(cast()*arg, fmt, addrs);
294 					}();
295 				} else {
296 					// TODO: Print something like this instead?:
297 					// const st = sm.fpwrite1_string(" -> ");
298 					// sm.fpwrite1_char('?');
299 				}
300 			}
301         }
302 	} else {
303 		static assert(0, "TODO: Handle argument of type " ~ T.stringof);
304 	}
305 }
306 
307 int fpwrite1ArrayValueExceptString(T)(scope FILE* sm, in T arg, in Format fmt, scope ref Addrs addrs, bool truncated) {
308 	const st1 = sm.fpwrite1_char(fmt.arrayPrefix);
309 	static if (is(immutable T == immutable void[])) {
310 		if (fmt.showVoidArrayValues) {
311 			ubyte[] bytes;
312 			() @trusted {
313 				bytes = cast(ubyte[])arg/+to mutable to avoid template-bloat+/;
314 			}();
315 			sm.fpwriteArrayValues(bytes, fmt, addrs, truncated); // TODO: perhaps show as hex instead
316 		}
317 		else if (arg.length != 0) // if any unprintable elements
318 			const st = sm.fpwrite1_string("?"); // indicate that
319 	} else {
320 		sm.fpwriteArrayValues(arg[], fmt, addrs, truncated);
321 	}
322 	const st2 = sm.fpwrite1_char(fmt.arraySuffix);
323 	return 0;
324 }
325 
326 int fpwriteArrayValues(E)(scope FILE* sm, in E[] arg, in Format fmt, scope ref Addrs addrs, bool truncated) {
327 	Format eltFmt = fmt; // element format
328 	static if (!is(E == struct) && !is(E == class))
329 		eltFmt.indentation = null;
330 	foreach (const i, ref elt; arg) {
331 		if (i)
332 			const st1_ = sm.fpwrite1_string(fmt.arrayElementSeparator);
333 		sm.fpwrite1(elt, eltFmt, addrs);
334 	}
335 	if (truncated && arg.length != 0)
336 		sm.fpwrite1_string(", …");
337 	return 0;
338 }
339 
340 void fpwriteAggregate(T)(scope FILE* sm, in T arg, in Format fmt, scope ref Addrs addrs)
341 if (is(T == struct) || is(T == class)) {
342 	const stT = sm.fpwrite1_string(T.stringof);
343 	const stP = sm.fpwrite1_char(fmt.aggregatePrefix);
344 	sm.fpwriteFieldsOf(arg, fmt, addrs);
345 	const stS = sm.fpwrite1_char(fmt.aggregateSuffix);
346 }
347 
348 void fpwriteFieldsOf(T)(scope FILE* sm, in T arg, in Format fmt, scope ref Addrs addrs)
349 if (is(T == struct) || is(T == class)) {
350 	import std.traits : isCallable;
351 	size_t i;
352 	foreach (memberName; __traits(allMembers, T)) {
353 		static if (memberName != "__ctor" && memberName != "__dtor" && memberName != "__postblit" &&
354 				   memberName != "__xctor" && memberName != "__xdtor" && memberName != "__xpostblit" &&
355 				   !is(__traits(getMember, arg, memberName))) { /* exclude type members */
356 			static if (__traits(compiles, { const _ = __traits(getMember, arg, memberName); } )) { /+ TODO: try to replace this with another __traits() +/
357 				alias Member = typeof(__traits(getMember, arg, memberName));
358 				static if (!isCallable!Member) {
359 					if (i != 0)
360 						const sta = sm.fpwrite1_string(fmt.aggregateFieldSeparator);
361 					if (fmt.multiLine)
362 						const st1 = sm.fpwrite1_char('\n');
363 					Format fieldFmt = fmt; // field format
364 					fieldFmt.quoteChars = true; // mimics `std.stdio`
365 					fieldFmt.quoteStrings = true; // mimics `std.stdio`
366 					fieldFmt.level += 1;
367 					sm.indent(fieldFmt);
368 					if (fmt.showFieldTypes) {
369 						const st1 = sm.fpwrite1_string(Member.stringof);
370 						const st2 = sm.fpwrite1_char(' ');
371 					}
372 					if (fmt.showFieldNames) {
373 						const st1 = sm.fpwrite1_string(memberName);
374 						const st2 = sm.fpwrite1_string(": ");
375 					}
376 					() @trusted {
377 						static if (is(Member == enum))
378 							sm.fpwrite1_enum(__traits(getMember, arg, memberName), fieldFmt, addrs);
379 						else {
380 							try {
381 								sm.fpwrite1(cast()__traits(getMember, arg, memberName), fieldFmt, addrs); // TODO: remove cast()
382 							} catch (Exception _) {
383 								const st = sm.fpwrite1_string("TODO: Avoid this Exception that is maybe triggered by casting away shared above");
384 							}
385 						}
386 					}();
387 					i += 1;
388 				}
389 			}
390 		}
391 	}
392 }
393 
394 int fpwrite1(scope FILE* sm, in   char arg, in Format fmt, scope ref Addrs addrs) @trusted nothrow @nogc {
395 	return sm.fpwrite1_char(arg, fmt.quoteChars);
396 }
397 int fpwrite1(scope FILE* sm, in  wchar arg, in Format fmt, scope ref Addrs addrs) @trusted nothrow @nogc {
398 	return sm.fpwrite1_wchar(arg, fmt.quoteChars);
399 }
400 int fpwrite1(scope FILE* sm, in  dchar arg, in Format fmt, scope ref Addrs addrs) @trusted nothrow @nogc {
401 	return sm.fpwrite1_dchar(arg, fmt.quoteChars);
402 }
403 
404 int fpwrite1(scope FILE* sm, in char[] arg, in Format fmt, scope ref Addrs addrs) @trusted nothrow @nogc {
405 	return sm.fpwrite1_string(arg, fmt.quoteStrings);
406 }
407 int fpwrite1(scope FILE* sm, in wchar[] arg, in Format fmt, scope ref Addrs addrs) @trusted nothrow @nogc {
408 	return sm.fpwrite1_wstring(arg, fmt.quoteStrings);
409 }
410 int fpwrite1(scope FILE* sm, in dchar[] arg, in Format fmt, scope ref Addrs addrs) @trusted nothrow @nogc {
411 	return sm.fpwrite1_dstring(arg, fmt.quoteStrings);
412 }
413 
414 int fpwrite1(scope FILE* sm, in   bool arg, in Format fmt, scope ref Addrs addrs) @trusted nothrow @nogc {
415 	return sm.fpwrite1_string(arg ? "true" : "false");
416 }
417 int fpwrite1(scope FILE* sm, in   byte arg, in Format fmt, scope ref Addrs addrs) @trusted nothrow @nogc {
418 	return sm.fprintf("%hhd".ptr, arg);
419 }
420 int fpwrite1(scope FILE* sm, in  ubyte arg, in Format fmt, scope ref Addrs addrs) @trusted nothrow @nogc {
421 	return sm.fprintf("%hhu".ptr, arg);
422 }
423 
424 int fpwrite1(scope FILE* sm, in  short arg, in Format fmt, scope ref Addrs addrs) @trusted nothrow @nogc {
425 	return sm.fprintf("%hd".ptr, arg);
426 }
427 int fpwrite1(scope FILE* sm, in ushort arg, in Format fmt, scope ref Addrs addrs) @trusted nothrow @nogc {
428 	return sm.fprintf("%hu".ptr, arg);
429 }
430 
431 int fpwrite1(scope FILE* sm, in    int arg, in Format fmt, scope ref Addrs addrs) @trusted nothrow @nogc {
432 	return sm.fprintf("%d".ptr, arg);
433 }
434 int fpwrite1(scope FILE* sm, in   uint arg, in Format fmt, scope ref Addrs addrs) @trusted nothrow @nogc {
435 	return sm.fprintf("%u".ptr, arg);
436 }
437 
438 int fpwrite1(scope FILE* sm, in   long arg, in Format fmt, scope ref Addrs addrs) @trusted nothrow @nogc {
439 	return sm.fprintf("%lld".ptr, arg);
440 }
441 int fpwrite1(scope FILE* sm, in  ulong arg, in Format fmt, scope ref Addrs addrs) @trusted nothrow @nogc {
442 	return sm.fprintf("%llu".ptr, arg);
443 }
444 
445 int fpwrite1(scope FILE* sm, in  float arg, in Format fmt, scope ref Addrs addrs) @trusted nothrow @nogc {
446 	return sm.fprintf("%g".ptr, arg);
447 }
448 int fpwrite1(scope FILE* sm, in double arg, in Format fmt, scope ref Addrs addrs) @trusted nothrow @nogc {
449 	return sm.fprintf("%lg".ptr, arg);
450 }
451 int fpwrite1(scope FILE* sm, in real arg, in Format fmt, scope ref Addrs addrs) @trusted nothrow @nogc {
452 	return sm.fprintf("%Lg".ptr, arg);
453 }
454 
455 int fpwrite1_enum(T)(scope FILE* sm, in T arg, in Format fmt, scope ref Addrs addrs) @trusted nothrow @nogc if (is(T == enum)) {
456 	if (fmt.showEnumeratorEnumType) {
457 		sm.fpwrite1_string(T.stringof, false);
458 		sm.fpwrite1_char('.', false);
459 	}
460 	return sm.fpwrite1_string(arg.enumToString!T(fmt, "__unknown__"), false);
461 }
462 
463 int fpwrite1_char(scope FILE* sm, in char arg, in bool quote = false) @trusted nothrow @nogc {
464 	if (quote) { const st1 = fputc('\'', sm); }
465 	const st = fputc(arg, sm);
466 	if (st == EOF) {} /+ TODO: throw or return error status +/
467 	if (quote) { const st1 = fputc('\'', sm); }
468 	return 0;
469 }
470 
471 int fpwrite1_wchar(scope FILE* sm, in wchar arg, in bool quote = false) @trusted nothrow @nogc {
472 	if (quote) { const st1 = fputc('\'', sm); }
473 	const st = fputwc(arg, sm);
474 	if (st == EOF) {} /+ TODO: throw or return error status +/
475 	if (quote) { const st1 = fputc('\'', sm); }
476 	return 0;
477 }
478 
479 int fpwrite1_dchar(scope FILE* sm, in dchar arg, in bool quote = false) @trusted nothrow @nogc {
480 	if (quote) { const st1 = fputc('\'', sm); }
481 	const st = fputwc(arg, sm);
482 	if (st == EOF) {} /+ TODO: throw or return error status +/
483 	if (quote) { const st1 = fputc('\'', sm); }
484 	return 0;
485 }
486 
487 int fpwrite1_string(scope FILE* sm, in char[] arg, in bool quote = false) @trusted nothrow @nogc {
488 	if (quote) { const st1 = fputc('"', sm); }
489 	typeof(return) st;
490 	if (arg.length)
491 		st = cast(typeof(return))c_fwrite(&arg[0], 1, arg.length, sm);
492 	if (quote) { const st2 = fputc('"', sm); }
493 	return st;
494 }
495 
496 int fpwrite1_wstring(scope FILE* sm, in wchar[] arg, in bool quote = false) @trusted nothrow @nogc {
497 	if (quote) { const st1 = fputc('"', sm); }
498 	typeof(return) st;
499 	if (arg.length)
500 		st = cast(typeof(return))c_fwrite(&arg[0], 1, arg.length, sm);
501 	if (quote) { const st2 = fputc('"', sm); }
502 	return st;
503 }
504 
505 int fpwrite1_dstring(scope FILE* sm, in dchar[] arg, in bool quote = false) @trusted nothrow @nogc {
506 	if (quote) { const st1 = fputc('"', sm); }
507 	typeof(return) st;
508 	if (arg.length)
509 		st = cast(typeof(return))c_fwrite(&arg[0], 1, arg.length, sm);
510 	if (quote) { const st2 = fputc('"', sm); }
511 	return st;
512 }
513 
514 string enumToString(T)(in T arg, in Format fmt, string defaultValue) if (is(T == enum)) {
515 	switch (arg) { // instead of slower `std.conv.to`:
516 		static foreach (member; __traits(allMembers, T)) { // instead of slower `EnumMembers`
517 		case __traits(getMember, T, member):
518 			return member;
519 		}
520 	default:
521 		return defaultValue;
522 	}
523 }
524 
525 void indent(scope FILE* sm, in Format fmt) nothrow @nogc {
526 	if (fmt.multiLine)
527 		foreach (_; 0 .. fmt.level)
528 			const int st = sm.fpwrite1_string(fmt.indentation, false);
529 }
530 
531 ///
532 version (show)
533 @safe nothrow unittest {
534 	import core.simd;
535 	static struct Uncopyable { this(this) @disable; int _x; }
536 	scope const int* x = new int(42);
537 	enum NormalEnum {
538 		first = 0,
539 		second = 1,
540 	}
541 	writeln(NormalEnum.first);
542 	writeln(NormalEnum.second);
543 	version (none) enum StringEnum { // TODO: use
544 		OPENED = "opened",
545 		MERGED = "merged",
546 	}
547 	class Base {
548 		int baseField1;
549 		int baseField2;
550 	}
551 	class Derived : Base {
552 		this(int derivedField1, int derivedField2) {
553 			this.derivedField1 = derivedField1;
554 			this.derivedField2 = derivedField2;
555 		}
556 		int derivedField1;
557 		int derivedField2;
558 	}
559 	struct T {
560 		int x = 111, y = 222;
561 	}
562 	struct U {
563 		const bool b_f = false;
564 		const bool b_t = true;
565 	}
566 	struct V {
567 		const char ch_a = 'a';
568 		const wchar wch = 'ä';
569 		const dchar dch = 'ö';
570 		const c_ch = 'b';
571 		const i_ch = 'c';
572 	}
573 	struct W {
574 		const str = "åäö";
575 		const wstring wstr = "åäö";
576 		const dstring dstr = "åäö";
577 		const ubyte[] ua = [1,2,3];
578 		const void[] va = cast(void[])[1,2,3];
579 	}
580 	struct X {
581 		const  byte[3] ba = [byte.min, 0, byte.max];
582 		const short[3] sa = [short.min, 0, short.max];
583 		const   int[3] ia = [int.min, 0, int.max];
584 		const  long[3] la = [long.min, 0, long.max];
585 	}
586 	struct Y {
587 		const  float[3] fa = [-float.infinity, 3.14, +float.infinity];
588 		const double[3] da = [-double.infinity, 3.333333333, +double.infinity];
589 		const   real[3] ra = [-real.infinity, 0, +real.infinity];
590 	}
591 	struct S {
592 		T t;
593 		U u;
594 		V v;
595 		W w;
596 		X x;
597 		Y y;
598 		int ix = 32;
599 		const int iy = 42;
600 		immutable int iz = 52;
601 		const(int)* null_p;
602 		const(int)* xp;
603 		NormalEnum normalEnum;
604 		version (none) StringEnum stringEnum; // TODO: use
605 		const aa = [1:1, 2:2];
606 		float4 f4;
607 		void* voidPtr;
608 		Derived derived;
609 		Base baseBeingDerived;
610 	}
611 
612 	S s;
613 	s.xp = x;
614 	s.voidPtr = new int(42);
615 	s.derived = new Derived(33, 44);
616 	s.baseBeingDerived = new Derived(55, 66);
617 
618 	writeln(Uncopyable.init);
619 
620 	Format fmt;
621 
622 	fmt = Format.init;
623 	fmt.showFieldNames = true;
624 	pwriteln(fmt, s);
625 
626 	fmt = Format.init;
627 	fmt.showFieldTypes = true;
628 	pwriteln(fmt, s);
629 
630 	fmt = Format.init;
631 	fmt.showPointerValues = true;
632 	pwriteln(fmt, s);
633 
634 	fmt = Format.init;
635 	fmt.showEnumeratorEnumType = true;
636 	pwriteln(fmt, s);
637 
638 	fmt = Format.fancy;
639 	fmt.showFieldTypes = false;
640 	pwriteln(fmt, s);
641 
642 	fmt = Format.init;
643 	pwriteln(fmt, s);
644 
645 	// import std.stdio : writeln;
646 	// debug writeln(s);
647 }