1 module deredux;
2 
3 import std.array : appender;
4 import std.format : formattedWrite;
5 import std.traits;
6 
7 private struct ImmuWrapper(T) {
8 	union {
9 		immutable(T) immu;
10 		T nImmu;
11 	}
12 
13 	this(T v) {
14 		this.nImmu = v;
15 	}
16 }
17 
18 private union Parameters {
19 	ubyte Ubyte;
20 	ushort Ushort;
21 	uint Uint;
22 	ulong Ulong;
23 	byte Byte;
24 	short Short;
25 	int Int;
26 	long Long;
27 	float Float;
28 	double Double;
29 	real Real;
30 	string String;
31 	wstring Wstring;
32 	dstring Dstring;
33 	bool Bool;
34 }
35 
36 private struct StringParameter {
37 	import fixedsizearray;
38 	import taggedalgebraic;
39 
40 	string funcName;
41 	int line;
42 	FixedSizeArray!(TaggedAlgebraic!Parameters,16) parameters; 
43 
44 	this(Args...)(string funcName, int line, Args args) {
45 		this.funcName = funcName;
46 		this.line = line;
47 		foreach(it; args) {
48 			this.parameters.insertBack(it);
49 		}
50 	}
51 }
52 
53 /** State can be your single source of truth if you let it.
54 Call `exe` to run a function and use to `peek` to have a look at the last
55 version of `immutable(Type)`. You should not hold a copy of the data returned
56 by `peek`.
57 */
58 struct State(Type,int Size = 16) {
59 	import std.typecons : Rebindable;
60 	import std.variant : Variant;
61 	import core.sync.mutex;
62 	import fixedsizearray;
63 	import taggedalgebraic;
64 
65 	FixedSizeArray!(ImmuWrapper!Type,Size) state;
66 	FixedSizeArray!(StringParameter,Size - 1) parameters;
67 
68 	this() @disable;
69 
70 	/** Construct the State object with an `initState`.
71 	*/
72 	this(Type initState) {
73 		this.state.insertBack(ImmuWrapper!Type(initState));
74 	}
75 
76 	/** Execute the function `F`  on the current value with parameters
77 	`Args...`.
78 	*/
79 	void exe(F,int line = __LINE__ ,Args...)(F f, Args args) {
80 		if(this.state.length + 1 == this.state.capacity()) {
81 			this.state.removeFront();
82 			this.parameters.removeFront();
83 		}
84 		this.state.insertBack(ImmuWrapper!Type(f(this.state.back.immu, args)));
85 		this.parameters.insertBack(StringParameter(
86 				fullyQualifiedName!(F), line, args
87 		));
88 	}
89 
90 	/** Peek at the current element.
91 	*/
92 	ref immutable(Type) peek() {
93 		return this.state.back.immu;
94 	}
95 
96 	/** Call this to get an output of the last few states and the passed
97 	parameter.
98 	*/
99 	string toString() const {
100 		import std.array : appender;
101 		auto app = appender!string();
102 
103 		this.toString(app);
104 		return app.data;
105 	}
106 
107 	/// Ditto
108 	void toString(D)(D app) const {
109 		import std.stdio;
110 		import std.format : formattedWrite;
111 
112 		for(int i = 0; i < this.parameters.length; ++i) {
113 			formattedWrite(app, "%2d %s line %d: %s(", this.parameters.length - i,
114 					this.state[i].immu, this.parameters[i].line,
115 					this.parameters[i].funcName
116 			);
117 			bool first = true;
118 			foreach(it; this.parameters[i].parameters[]) {
119 				if(first) {
120 					formattedWrite(app, "%s", it);
121 				} else {
122 					formattedWrite(app, ",%s", it);
123 				}
124 				first = false;
125 			}
126 			formattedWrite(app, ")\n");
127 		}
128 		formattedWrite(app, "%2d %s", 0, this.state.back.immu);
129 	}
130 }
131 
132 /// Ditto
133 unittest {
134 	struct Foo {
135 		int value;
136 	}
137 	
138 	struct FooRedux {
139 		Foo fun(const(Foo) foo) {
140 			return Foo(foo.value + 2);
141 		}
142 	}
143 	
144 	Foo bar(const(Foo) foo, int i) {
145 		return Foo(foo.value + i);
146 	}
147 	import std.stdio;
148 	import exceptionhandling;
149 
150 	const begin = 1337;
151 
152 	auto fooState = State!(Foo)(Foo(begin));
153 
154 	int cnt = 0;
155 	for(int i = 1; i <= 126; ++i) {
156 		fooState.exe(&bar, i);
157 		cnt += i;
158 		cast(void)assertEqual(fooState.peek().value, begin + cnt);
159 	}
160 
161 	cast(void)assertEqual(fooState.peek().value, begin + cnt);
162 
163 	auto f = fooState.peek();
164 
165 	FooRedux fr;
166 	fooState.exe(&fr.fun);
167 	writeln(fooState.toString());
168 }