#!/usr/bin/env dub
/+ dub.sdl:
name "property_tree_erased_descent"
targetPath "build"
dflags "-preview=in" "-preview=dip1000"
buildType "checked" {
buildOptions "optimize" "inline" "debugInfo"
}
+/
/**
* The escape from the compile-time recursion limit, and what it costs.
*
* [`reflect-descent.d`](./reflect-descent.d) shows that a descent written as one
* CTFE walk over `T` **fails to build** on a recursive type — the visited-type
* set is mandatory, and it cuts a type the *second* time it appears anywhere on
* a path.
*
* The Tier-2 pass found that the field does not have this problem, and why.
* Rust's [derive-macro inspectors](../derive-macro-inspectors.md) generate one
* impl per type whose child walk crosses a `&mut dyn` boundary, so recursion is
* a **runtime call**; [DevTools](../devtools-object-inspector.md) fetches each
* level on expansion, so a cyclic graph is finite work per click; and
* [`react-jsonschema-form`](../react-jsonschema-form.md) renders a detected
* `$ref` cycle as an **Expand placeholder** — one level per press, with the cut
* visible to the reader.
*
* This program is those three answers in D:
*
* 1. `rowsOf!T` is still compile-time — it reflects fields, labels and leaf-ness
* from the type — but the child walk is **erased behind a delegate**, so the
* template instantiates once per type rather than once per path, and a
* self-referential type compiles.
* 2. Descent is driven by a **budget**, not by the type: rows are produced to a
* requested depth and anything deeper becomes a `cut` row.
* 3. A `cut` row carries the delegate that would continue, so "expand" is one
* more bounded call — the rjsf affordance, in cells.
*
* The cost is stated honestly at the end: the erasure is a virtual call and an
* allocation per open node, which is exactly what the CTFE walk avoided.
*
* Run: `dub run --single erased-descent.d`
*/
module (module) property_tree_erased_descentThe escape from the compile-time recursion limit, and what it costs.
reflect-descent.d shows that a descent written as one
CTFE walk over T fails to build on a recursive type — the visited-type
set is mandatory, and it cuts a type the second time it appears anywhere on
a path.
The Tier-2 pass found that the field does not have this problem, and why.
Rust's derive-macro inspectors generate one
impl per type whose child walk crosses a &mut dyn boundary, so recursion is
a runtime call; DevTools fetches each
level on expansion, so a cyclic graph is finite work per click; and
react-jsonschema-form renders a detected
$ref cycle as an Expand placeholder — one level per press, with the cut
visible to the reader.
This program is those three answers in D:
rowsOf!T is still compile-time — it reflects fields, labels and leaf-ness
from the type — but the child walk is erased behind a delegate, so the
template instantiates once per type rather than once per path, and a
self-referential type compiles.
Descent is driven by a budget, not by the type: rows are produced to a
requested depth and anything deeper becomes a cut row.
A cut row carries the delegate that would continue, so "expand" is one
more bounded call — the rjsf affordance, in cells.
The cost is stated honestly at the end: the erasure is a virtual call and an
allocation per open node, which is exactly what the CTFE walk avoided.
Run
dub run --single erased-descent.d
property_tree_erased_descent;
import (package) stdstd.(module) std.stdioCategory Symbols File handles _popen File isFileHandle openNetwork stderr stdin stdout Reading chunks lines readf readfln readln Writing toFile write writef writefln writeln Misc KeepTerminator LockType StdioException
Standard I/O functions that extend core.stdc.stdio. core.stdc.stdio
is publically imported when importing std.stdio.
There are three layers of I/O:
The lowest layer is the operating system layer. The two main schemes are Windows and Posix.
C's stdio.h which unifies the two operating system schemes.
std.stdio, this module, unifies the various stdio.h implementations into
a high level package for D programs.
Source
std/stdio.d
stdio : (alias template) property_tree_erased_descent.writefln = std.stdio.writefln(alias fmt, A...)(A args) if (isSomeString!(typeof(fmt)))Equivalent to writef(fmt, args, '\n').
writefln, (alias template) property_tree_erased_descent.writeln = std.stdio.writeln(T...)(T args)Equivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln;
import (package) stdstd.(module) std.traitsTemplates which extract information about types and symbols at compile time.
Category Templates Symbol Name traits fullyQualifiedName mangledName moduleName packageName Function traits isFunction arity functionAttributes hasFunctionAttributes functionLinkage FunctionTypeOf isSafe isUnsafe isFinal ParameterDefaults ParameterIdentifierTuple ParameterStorageClassTuple Parameters ReturnType SetFunctionAttributes variadicFunctionStyle Aggregate Type traits BaseClassesTuple BaseTypeTuple classInstanceAlignment EnumMembers FieldNameTuple Fields hasAliasing hasElaborateAssign hasElaborateCopyConstructor hasElaborateDestructor hasElaborateMove hasIndirections hasMember hasStaticMember hasNested hasUnsharedAliasing InterfacesTuple isInnerClass isNested MemberFunctionsTuple RepresentationTypeTuple TemplateArgsOf TemplateOf TransitiveBaseTypeTuple Type Conversion CommonType AllImplicitConversionTargets ImplicitConversionTargets CopyTypeQualifiers CopyConstness isAssignable isCovariantWith isImplicitlyConvertible isQualifierConvertible Type Constructors InoutOf ConstOf SharedOf SharedInoutOf SharedConstOf SharedConstInoutOf ImmutableOf QualifierOf Categories of types allSameType ifTestable isType isAggregateType isArray isAssociativeArray isAutodecodableString isBasicType isBoolean isBuiltinType isCopyable isDynamicArray isEqualityComparable isFloatingPoint isIntegral isNarrowString isConvertibleToString isNumeric isOrderingComparable isPointer isScalarType isSigned isSIMDVector isSomeChar isSomeString isStaticArray isUnsigned Type behaviours isAbstractClass isAbstractFunction isCallable isDelegate isExpressions isFinalClass isFinalFunction isFunctionPointer isInstanceOf isIterable isMutable isSomeFunction isTypeTuple General Types ForeachType KeyType Largest mostNegative OriginalType PointerTarget Signed Unconst Unshared Unqual Unsigned ValueType Promoted Misc lvalueOf rvalueOf Select select User-Defined Attributes hasUDA getUDAs getSymbolsByUDA
Source
std/traits.d
traits : (alias template) property_tree_erased_descent.FieldNameTuple = std.traits.FieldNameTuple(T)Get as an expression tuple the names of the fields of a struct, class, or
union. This consists of the fields that take up memory space, excluding the
hidden fields like the virtual function table pointer or a context pointer
for nested types.
Inherited fields (for classes) are not included.
If T isn't a struct, class, interface or union, an
expression tuple with an empty string is returned.
FieldNameTuple, (alias template) property_tree_erased_descent.Fields = std.traits.Fields(T)Get as a tuple the types of the fields of a struct, class, or union.
This consists of the fields that take up memory space,
excluding the hidden fields like the virtual function
table pointer or a context pointer for nested types.
If T isn't a struct, class, interface or union returns a tuple
with one element T.
Fields, (alias template) property_tree_erased_descent.isAggregateType = std.traits.isAggregateType(T)Detect whether type T is an aggregate type.
isAggregateType, (alias template) property_tree_erased_descent.isPointer = std.traits.isPointer(T)Detect whether type T is a pointer.
isPointer,
(alias template) property_tree_erased_descent.PointerTarget = std.traits.PointerTarget(T : T*)Returns the target type of a pointer.
PointerTarget;
@safe:
/// One presented row. `expand` is non-null exactly when the row was cut.
struct (struct) property_tree_erased_descent.RowOne presented row. expand is non-null exactly when the row was cut.
Row
{
(alias) object.string = stringstring (field) string property_tree_erased_descent.Row.pathpath;
(alias) object.string = stringstring (field) string property_tree_erased_descent.Row.typetype;
(alias) object.size_t = ulongsize_t (field) ulong property_tree_erased_descent.Row.depthdepth;
bool (field) bool property_tree_erased_descent.Row.isLeafisLeaf;
(alias) property_tree_erased_descent.Rows = property_tree_erased_descent.Row[]Rows delegate() @safe (field) property_tree_erased_descent.Row[] delegate() @safe property_tree_erased_descent.Row.expandnull unless this row is a cut
expand; /// null unless this row is a cut
}
alias (alias) property_tree_erased_descent.Rows = property_tree_erased_descent.Row[]Rows = Row[];
/// The erasure boundary: a node knows how to produce its own child rows and
/// nothing about who asked. This is the `&mut dyn EguiProbe` of the Rust family.
alias (alias) property_tree_erased_descent.ChildSource = property_tree_erased_descent.Row[] delegate(ulong budget, string prefix, ulong depth) @safeThe erasure boundary: a node knows how to produce its own child rows and
nothing about who asked. This is the &mut dyn EguiProbe of the Rust family.
ChildSource = Rows delegate((alias) object.size_t = ulongsize_t (parameter) ulong budgetbudget, (alias) object.string = stringstring (parameter) string prefixprefix, (alias) object.size_t = ulongsize_t (parameter) ulong depthdepth) @safe;
private enum bool (constant) bool property_tree_erased_descent.isLeafType!float = trueisLeafType(T) = is((alias) T = floatT == string) || !(template instance) std.traits.isAggregateType!floatisAggregateType!(alias) T = floatT;
/// Presents `value` as rows, descending at most `budget` levels.
///
/// The recursion below is a **runtime** call through `ChildSource`, so this
/// template is instantiated once per type — not once per path — and a type that
/// contains itself is ordinary rather than fatal.
(alias) property_tree_erased_descent.Rows = property_tree_erased_descent.Row[]Rows property_tree_erased_descent.Row[] property_tree_erased_descent.rowsOf!(property_tree_erased_descent.Material)(ref property_tree_erased_descent.Material value, ulong budget, string prefix = "", ulong depth = 0LU) @safePresents value as rows, descending at most budget levels.
The recursion below is a runtime call through ChildSource, so this
template is instantiated once per type — not once per path — and a type that
contains itself is ordinary rather than fatal.
rowsOf(T)(ref (alias) T = property_tree_erased_descent.MaterialT (parameter) property_tree_erased_descent.Material valuevalue, (alias) object.size_t = ulongsize_t (parameter) ulong budgetbudget, (alias) object.string = stringstring (parameter) string prefixprefix = "", (alias) object.size_t = ulongsize_t (parameter) ulong depthdepth = 0)
{
(alias) property_tree_erased_descent.Rows = property_tree_erased_descent.Row[]Rows (local variable) property_tree_erased_descent.Row[] rowsrows;
static foreach (i, name; (constant) string property_tree_erased_descent.Material.fun!(name).NameOf = "name"FieldNameTuple!T)
{{
alias (alias) property_tree_erased_descent.rowsOf!(property_tree_erased_descent.Material).F = stringF = (alias) std.traits.Fields!(property_tree_erased_descent.Material) = (string, property_tree_erased_descent.Vec2)Get as a tuple the types of the fields of a struct, class, or union.
This consists of the fields that take up memory space,
excluding the hidden fields like the virtual function
table pointer or a context pointer for nested types.
If T isn't a struct, class, interface or union returns a tuple
with one element T.
Examples
import std.meta : AliasSeq;
struct S { int x; float y; }
static assert(is(Fields!S == AliasSeq!(int, float)));
Fields!(alias) T = property_tree_erased_descent.MaterialT[(constant) ulong property_tree_erased_descent.rowsOf!(property_tree_erased_descent.Material).i = 0LUi];
static if ((template instance) std.traits.isPointer!stringisPointer!(alias) property_tree_erased_descent.rowsOf!(property_tree_erased_descent.Material).F = stringF && (template instance) isAggregateType!(PointerTarget!F)isAggregateType!((alias) T = property_tree_erased_descent.NodePointerTarget!(unresolved type) FF))
alias (alias) property_tree_erased_descent.rowsOf!(property_tree_erased_descent.Node).Target = property_tree_erased_descent.NodeTarget = (alias) T = property_tree_erased_descent.NodePointerTarget!(alias) property_tree_erased_descent.rowsOf!(property_tree_erased_descent.Node).F = property_tree_erased_descent.Node*F;
else
alias (alias) property_tree_erased_descent.rowsOf!(property_tree_erased_descent.Material).Target = stringTarget = (alias) property_tree_erased_descent.rowsOf!(property_tree_erased_descent.Material).F = stringF;
const (local variable) const(string) pathpath = (parameter) string prefixprefix ~ (constant) string property_tree_erased_descent.rowsOf!(property_tree_erased_descent.Material).name = "name"name;
enum (constant) bool property_tree_erased_descent.rowsOf!(property_tree_erased_descent.Material).leaf = trueleaf = (template instance) property_tree_erased_descent.isLeafType!stringisLeafType!(alias) property_tree_erased_descent.rowsOf!(property_tree_erased_descent.Material).Target = stringTarget;
static if (leaf)
{
(local variable) property_tree_erased_descent.Row[] rowsrows ~= (struct) property_tree_erased_descent.RowOne presented row. expand is non-null exactly when the row was cut.
Row((local variable) const(string) pathpath, (darray) stringF.(constant) string string.stringof = "string"stringof, (parameter) ulong depthdepth, true, null);
}
else
{
// The child walk, erased. Capturing it as a delegate is what stops
// the template from re-entering itself at compile time.
(alias) property_tree_erased_descent.ChildSource = property_tree_erased_descent.Row[] delegate(ulong budget, string prefix, ulong depth) @safeThe erasure boundary: a node knows how to produce its own child rows and
nothing about who asked. This is the &mut dyn EguiProbe of the Rust family.
ChildSource (local variable) property_tree_erased_descent.Row[] delegate(ulong budget, string prefix, ulong depth) @safe sourcesource = ((alias) object.size_t = ulongsize_t (parameter) ulong bb, (alias) object.string = stringstring (parameter) string pp, (alias) object.size_t = ulongsize_t (parameter) ulong dd) @safe {
static if ((template instance) std.traits.isPointer!(property_tree_erased_descent.Vec2)isPointer!(alias) property_tree_erased_descent.rowsOf!(property_tree_erased_descent.Material).F = property_tree_erased_descent.Vec2F)
{
auto (local variable) property_tree_erased_descent.Node* targettarget = __traits(getMember, value, name);
if ((local variable) property_tree_erased_descent.Node* targettarget is null)
return (darray) property_tree_erased_descent.Row[]Rows.(constant) property_tree_erased_descent.Row[] property_tree_erased_descent.Row[].init = nullinit;
return property_tree_erased_descent.Row[] property_tree_erased_descent.rowsOf!(property_tree_erased_descent.Node)(ref property_tree_erased_descent.Node value, ulong budget, string prefix = "", ulong depth = 0LU) @safePresents value as rows, descending at most budget levels.
The recursion below is a runtime call through ChildSource, so this
template is instantiated once per type — not once per path — and a type that
contains itself is ordinary rather than fatal.
rowsOf!(alias) property_tree_erased_descent.rowsOf!(property_tree_erased_descent.Node).Target = property_tree_erased_descent.NodeTarget(*(local variable) property_tree_erased_descent.Node* targettarget, (parameter) ulong bb, (parameter) string pp, (parameter) ulong dd);
}
else
return property_tree_erased_descent.Row[] property_tree_erased_descent.rowsOf!(property_tree_erased_descent.Vec2)(ref property_tree_erased_descent.Vec2 value, ulong budget, string prefix = "", ulong depth = 0LU) pure nothrow @safePresents value as rows, descending at most budget levels.
The recursion below is a runtime call through ChildSource, so this
template is instantiated once per type — not once per path — and a type that
contains itself is ordinary rather than fatal.
rowsOf!(alias) property_tree_erased_descent.rowsOf!(property_tree_erased_descent.Material).Target = property_tree_erased_descent.Vec2Target(__traits(getMember, value, name), (parameter) ulong bb, (parameter) string pp, (parameter) ulong dd);
};
if ((parameter) ulong budgetbudget == 0)
{
// The rjsf answer: a cut is a row with an affordance, not a hole.
(local variable) property_tree_erased_descent.Row[] rowsrows ~= (struct) property_tree_erased_descent.RowOne presented row. expand is non-null exactly when the row was cut.
Row((local variable) const(string) pathpath, (struct) property_tree_erased_descent.Vec2F.(constant) string property_tree_erased_descent.Vec2.stringof = "Vec2"stringof, (parameter) ulong depthdepth, false,
() @safe => (local variable) property_tree_erased_descent.Row[] delegate(ulong budget, string prefix, ulong depth) @safe sourcesource(1, (local variable) const(string) pathpath ~ ".", (parameter) ulong depthdepth + 1));
}
else
{
(local variable) property_tree_erased_descent.Row[] rowsrows ~= (struct) property_tree_erased_descent.RowOne presented row. expand is non-null exactly when the row was cut.
Row((local variable) const(string) pathpath, (struct) property_tree_erased_descent.Vec2F.(constant) string property_tree_erased_descent.Vec2.stringof = "Vec2"stringof, (parameter) ulong depthdepth, false, null);
(local variable) property_tree_erased_descent.Row[] rowsrows ~= (local variable) property_tree_erased_descent.Row[] delegate(ulong budget, string prefix, ulong depth) @safe sourcesource((parameter) ulong budgetbudget - 1, (local variable) const(string) pathpath ~ ".", (parameter) ulong depthdepth + 1);
}
}
}}
return (local variable) property_tree_erased_descent.Row[] rowsrows;
}
// ---------------------------------------------------------------------------
// The same subject as reflect-descent.d, including the type that reaches itself.
// ---------------------------------------------------------------------------
struct (struct) property_tree_erased_descent.Vec2Vec2
{
float (field) float property_tree_erased_descent.Vec2.xx = 0, (field) float property_tree_erased_descent.Vec2.yy = 0;
}
struct (struct) property_tree_erased_descent.MaterialMaterial
{
(alias) object.string = stringstring (field) string property_tree_erased_descent.Material.namename;
(struct) property_tree_erased_descent.Vec2Vec2 (field) property_tree_erased_descent.Vec2 property_tree_erased_descent.Material.offsetoffset;
}
struct (struct) property_tree_erased_descent.NodeNode
{
(alias) object.string = stringstring (field) string property_tree_erased_descent.Node.labellabel;
(struct) property_tree_erased_descent.Vec2Vec2 (field) property_tree_erased_descent.Vec2 property_tree_erased_descent.Node.positionposition;
(struct) property_tree_erased_descent.MaterialMaterial (field) property_tree_erased_descent.Material property_tree_erased_descent.Node.materialmaterial;
(struct) property_tree_erased_descent.NodeNode* (field) property_tree_erased_descent.Node* property_tree_erased_descent.Node.parentparent;
}
private void void property_tree_erased_descent.print(in property_tree_erased_descent.Row[] rows) @safeprint(in (alias) property_tree_erased_descent.Rows = property_tree_erased_descent.Row[]Rows (parameter) const(property_tree_erased_descent.Row[]) rowsrows)
{
foreach ((parameter) const(property_tree_erased_descent.Row) rr; (parameter) const(property_tree_erased_descent.Row[]) rowsrows)
{
char[] (local variable) char[] indentindent;
foreach ((local variable) ulong __; 0 .. (local variable) const(property_tree_erased_descent.Row) rr.(field) ulong property_tree_erased_descent.Row.depthdepth)
(local variable) char[] indentindent ~= " ";
const (local variable) const(string) markmark = (local variable) const(property_tree_erased_descent.Row) rr.(field) property_tree_erased_descent.Row[] delegate() @safe property_tree_erased_descent.Row.expandnull unless this row is a cut
expand !is null ? " ← cut (expandable)"
: (local variable) const(property_tree_erased_descent.Row) rr.(field) bool property_tree_erased_descent.Row.isLeafisLeaf ? "" : " [subtree]";
void std.stdio.writefln!(char, char[], string, string, string)(in char[] fmt, char[] __param_1, string __param_2, string __param_3, string __param_4) @safeEquivalent to writef(fmt, args, '\n').
writefln("%s%s : %s%s", (local variable) char[] indentindent, (local variable) const(property_tree_erased_descent.Row) rr.(field) string property_tree_erased_descent.Row.pathpath, (local variable) const(property_tree_erased_descent.Row) rr.(field) string property_tree_erased_descent.Row.typetype, (local variable) const(string) markmark);
}
}
void void D main() @safemain()
{
// A genuinely cyclic value: the node is its own parent.
(struct) property_tree_erased_descent.NodeNode (local variable) property_tree_erased_descent.Node rootroot = (struct) property_tree_erased_descent.NodeNode("root");
() @trusted { (local variable) property_tree_erased_descent.Node rootroot.(field) property_tree_erased_descent.Node* property_tree_erased_descent.Node.parentparent = &(local variable) property_tree_erased_descent.Node rootroot; }();
void std.stdio.writeln!string(string __param_0) @safeEquivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln("budget = 1 — one level, everything deeper is a cut row");
auto (local variable) property_tree_erased_descent.Row[] shallowshallow = property_tree_erased_descent.Row[] property_tree_erased_descent.rowsOf!(property_tree_erased_descent.Node)(ref property_tree_erased_descent.Node value, ulong budget, string prefix = "", ulong depth = 0LU) @safePresents value as rows, descending at most budget levels.
The recursion below is a runtime call through ChildSource, so this
template is instantiated once per type — not once per path — and a type that
contains itself is ordinary rather than fatal.
rowsOf((local variable) property_tree_erased_descent.Node rootroot, 1);
void property_tree_erased_descent.print(in property_tree_erased_descent.Row[] rows) @safeprint((local variable) property_tree_erased_descent.Row[] shallowshallow);
void std.stdio.writeln!()() @safeEquivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln();
void std.stdio.writefln!(char, ulong, ulong)(in char[] fmt, ulong __param_1, ulong __param_2) @safeEquivalent to writef(fmt, args, '\n').
writefln("%s rows, %s of them cuts", (local variable) property_tree_erased_descent.Row[] shallowshallow.(field) ulong property_tree_erased_descent.Row[].lengthlength, ulong property_tree_erased_descent.countCuts(in property_tree_erased_descent.Row[] rows) pure nothrow @nogc @safecountCuts((local variable) property_tree_erased_descent.Row[] shallowshallow));
void std.stdio.writeln!()() @safeEquivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln();
void std.stdio.writeln!string(string __param_0) @safeEquivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln("expanding the first cut — one more bounded level, on demand");
foreach ((parameter) property_tree_erased_descent.Row rr; (local variable) property_tree_erased_descent.Row[] shallowshallow)
{
if ((local variable) property_tree_erased_descent.Row rr.(field) property_tree_erased_descent.Row[] delegate() @safe property_tree_erased_descent.Row.expandnull unless this row is a cut
expand !is null)
{
void property_tree_erased_descent.print(in property_tree_erased_descent.Row[] rows) @safeprint((local variable) property_tree_erased_descent.Row rr.(field) property_tree_erased_descent.Row[] delegate() @safe property_tree_erased_descent.Row.expandnull unless this row is a cut
expand());
break;
}
}
void std.stdio.writeln!()() @safeEquivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln();
// The same value at three budgets: the row count is a function of the
// budget, not of the (infinite) value graph.
foreach ((local variable) int budgetbudget; 0 .. 4)
{
auto (local variable) property_tree_erased_descent.Row[] rowsrows = property_tree_erased_descent.Row[] property_tree_erased_descent.rowsOf!(property_tree_erased_descent.Node)(ref property_tree_erased_descent.Node value, ulong budget, string prefix = "", ulong depth = 0LU) @safePresents value as rows, descending at most budget levels.
The recursion below is a runtime call through ChildSource, so this
template is instantiated once per type — not once per path — and a type that
contains itself is ordinary rather than fatal.
rowsOf((local variable) property_tree_erased_descent.Node rootroot, (local variable) int budgetbudget);
void std.stdio.writefln!(char, int, ulong, ulong)(in char[] fmt, int __param_1, ulong __param_2, ulong __param_3) @safeEquivalent to writef(fmt, args, '\n').
writefln("budget=%s → %s rows (%s cuts)", (local variable) int budgetbudget, (local variable) property_tree_erased_descent.Row[] rowsrows.(field) ulong property_tree_erased_descent.Row[].lengthlength,
ulong property_tree_erased_descent.countCuts(in property_tree_erased_descent.Row[] rows) pure nothrow @nogc @safecountCuts((local variable) property_tree_erased_descent.Row[] rowsrows));
}
void std.stdio.writeln!()() @safeEquivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln();
void std.stdio.writeln!string(string __param_0) @safeEquivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln("cost: one delegate per open node, one virtual call per descent —");
void std.stdio.writeln!string(string __param_0) @safeEquivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln("what the compile-time walk avoided, in exchange for terminating.");
}
private (alias) object.size_t = ulongsize_t ulong property_tree_erased_descent.countCuts(in property_tree_erased_descent.Row[] rows) pure nothrow @nogc @safecountCuts(in (alias) property_tree_erased_descent.Rows = property_tree_erased_descent.Row[]Rows (parameter) const(property_tree_erased_descent.Row[]) rowsrows) pure nothrow @nogc
{
(alias) object.size_t = ulongsize_t (local variable) ulong nn;
foreach ((parameter) const(property_tree_erased_descent.Row) rr; (parameter) const(property_tree_erased_descent.Row[]) rowsrows)
if ((local variable) const(property_tree_erased_descent.Row) rr.(field) property_tree_erased_descent.Row[] delegate() @safe property_tree_erased_descent.Row.expandnull unless this row is a cut
expand !is null)
(local variable) ulong nn++;
return (local variable) ulong nn;
}