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