-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpolymorphicstruct.d
More file actions
406 lines (351 loc) · 17.8 KB
/
Copy pathpolymorphicstruct.d
File metadata and controls
406 lines (351 loc) · 17.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
/++
Author: Era Scarecrow <rtcvb32@yahoo.com>
Date: 11 Oct 2012
Project: Polymorphic Structs
Licence: GPLv3
Description: With the idea of polymorphism to add behavior to classes, classes sometimes are too bulky, too slow, too encumbersome for some tasks. Plus they live in the heap so allocating thousands of them can be slower. However if you don't need to extend the data with further mutable data and increaes the data signature, then there's no reason you can't make a struct polymorphic by nature.
<pre>
Limitations to be expected with using a polymorphic struct is that
1) Data size cannot change
2) The function names either can be normal or appended like with 'Poly_' to make the function polymorphic.
3) Polymorphic functions (in the same struct) need 'this.' prepended to it.
4) All functions need to be known ahead of time.
5) Template functions (outside of auto detection) cannot be used polymorphically.
6) Referring to previous/super functions unavaliable (for now).
7) No automatic type setup. Means you have to create the object and set it to it's inhereted type afterwards
</pre>
This is the initial experimental library. So there are likely bugs, not all the unittests are written, and the implimentation can change at any time. Example crypto library present as proof of concept with unittests. It relies on heavy use of mixins and can slow down compiling of your code.
---
auto crypto = PolyCryptoBase.factory("CryptoROT13");
crypto.setString("(Whfg n yvggyr pelcgb grfg)");
crypto.encipher;
assert(crypto.data.crypted == "(Just a little crypto test)");
---
Setting up your structures to use requires all the structs to have certain additions before hand. Use the below as a skeleton to add to your code.
---
struct Example {
//sets up Enum, _prefix, _types and aliases for polymorphing to the various structs.
mixin(expandTypes("Inner_Data_Struct_Name", "Prefix_", "Several,Struct_Names"));
//Enum { Several = 1, Struct_Names = 3} // is silently added by expandTypes
//inner data struct, must contain the polymorphic type identifyer.
struct Inner_Data_Struct_Name {
Enum polyMorphType; //defaults to base/first entry above, which is 'Several' in this case.
string somethingMutable; //any other variables here, just as an example.
}
//any and all data/accessible members here, but mostly your mutable data as above.
Inner_Data_Struct_Name data;
//This struct name, inner data's struct name, allocated/named variable containing the polymorphic state.
mixin PolyMorphic!("moduleNames","Example", Inner_Data_Struct_Name, "data");
}
//first named of structs is the base/root.
struct Several {
//required mixin, will fill in details for attaching to polymorph struct 'Example'
mixin PolyMorphicInclude!(Example);
/*DO NOT ADD MORE DATA/VARIABLES HERE!. Doing so will cause a compile-time error, the size
must be statically known ahead of time (for safety) and only in the base (Example) can
there the data be visible/accessible. static variables and enums should be okay to declare.
anything containing the 'prefix' noted before hand
are automatically polymorphable, so long as you don't call them with the prefix.*/
void Prefix_anotherFunc();
//no prefix, so likely not intended to be polymorphed, or only existing function
void someFunc() {
/*to call anotherFunc (even from this struct) you don't ever
include the prefix. opDispatch of the base struct (Example)
will add the prefix during checks to determine which struct
can be called.
*/
this.anotherFunc();
//accessing data that all structs have access to.
data.somethingMutable = "Several.anotherFunc called!";
}
}
struct Struct_Names {
//required mixin, minimal set up.
mixin PolyMorphicInclude!(Example);
/*this will only ever be called if the type is for 'Struct_Names', ie 'Enum.Struct_Names'. But if there
is another funciton inside this structure that calls 'anotherFunc', add the prefix to force polymorph
behavior.*/
void anotherFunc() {
//non existing function call handled by Example.opDispatch
this.someFunc();
//accessing data that all structs have access to.
data.somethingMutable = "Struct_Names.anotherFunc called!";
}
}
---
When only one possible matches are present then checks are never needed and jumps to the appropriate function, which Self optimizes. (If the function is in an inhereted but not the base class, it is possible to have an asserted runtime error) Const/non-const must match all other shared name declarations, if only using in a mutable state it won't complain, but if you try to call a mutable version of a function vs a const/immutable version that can be called/overloaded with a particular set of arguments, then the compiler will assert during compile-time and let you know. Tracing a polymorphed function that isn't calling is quite an annoying bug. 'static' is considered const as it never touches struct data.
Write the structures and debug them normally without adding polymorph abilities, then when you update rename/add the prefix
and the mixin, remove non-changing functions. Any functions calling the ones present in that struct need to have them prefixed.
+/
module smartmerged.polymorphicstruct;
import std.stdio;
import std.traits;
import std.conv;
import std.algorithm;
import std.range;
import std.exception;
enum MutableState { isMutable, isConst, isImmutable}
/*Checks if a particular call is legal, be it calling a function or accessing a variable.
depending on the results enumCheck and calling are prepared ahead of time for opDispatch
returns an array of the results. accessing/aliasing them individually slows compiling to a crawl.*/
template isLegalCall(string imports, string VariableAccess, string TypeCheck, string prefix, string funCall, string structName, MutableState state, Vars ...) {
mixin(expandImports(imports)); //where the structs are located/referred from.
enum objWithState = (state == MutableState.isConst ? "const ": "")
~ (state == MutableState.isImmutable ? "immutable " : "")
~ structName;
//if it's two prefixes, say Poly_Poly_, then it's recursively checking and instead should fail.
//prevents the checks below from getting stuck
static if (funCall.length >= prefix.length && prefix == funCall[0 .. prefix.length]) {
enum polyFunctionLike = false;
enum polyDataLike = false;
enum functionLike = false;
enum dataLike = false;
} else {
mixin("enum polyFunctionLike = hasMember!("~structName~",\""~prefix~funCall~"\") && is(typeof({"~objWithState~" x="~structName~"(); x."~prefix~funCall~"(Vars);}));"
~ "enum polyDataLike = Vars.length == 0 && hasMember!("~structName~",\""~prefix~funCall~"\") && is(typeof({"~objWithState~" x="~structName~"(); x."~prefix~funCall~";}));"
~ "enum functionLike = hasMember!("~structName~",\""~funCall~"\") && is(typeof({"~objWithState~" x="~structName~"(); x."~funCall~"(Vars);}));"
~ "enum dataLike = Vars.length == 0 && hasMember!("~structName~",\""~funCall~"\") && is(typeof({"~objWithState~" x="~structName~"(); x."~funCall~";}));"
);
}
//the following to detect when const/non-const is potentially possible for a call and errs
static if (state != MutableState.isMutable) {
mixin("enum polyFunctionLikeStateless = hasMember!("~structName~",\""~prefix~funCall~"\") && is(typeof({"~structName~" x="~structName~"(); x."~prefix~funCall~"(Vars);}));"
~ "enum polyDataLikeStateless = Vars.length == 0 && hasMember!("~structName~",\""~prefix~funCall~"\") && is(typeof({"~structName~" x="~structName~"(); x."~prefix~funCall~";}));"
~ "enum functionLikeStateless = hasMember!("~structName~",\""~funCall~"\") && is(typeof({"~structName~" x="~structName~"(); x."~funCall~"(Vars);}));"
~ "enum dataLikeStateless = Vars.length == 0 && hasMember!("~structName~",\""~funCall~"\") && is(typeof({"~structName~" x="~structName~"(); x."~funCall~";}));"
);
static assert(polyFunctionLike == polyFunctionLike, "Polymorph function "~funCall~" contains mutable and const mismatches");
static assert(polyDataLike == polyDataLikeStateless, "Polymorph function "~funCall~" (property?) contains mutable and const mismatches");
static assert(functionLike == functionLikeStateless, "Function "~funCall~" contains mutable and const mismatches");
static assert(dataLike == dataLikeStateless, "Function "~funCall~" (property?) contains mutable and const mismatches");
}
enum enumCheck = "if (("~VariableAccess~"."~TypeCheck~" & Enum."~structName~") == Enum."~structName~")";
enum calling = "return (cast("~objWithState~") "~VariableAccess~")."~ending(prefix, funCall, functionLike, dataLike, polyFunctionLike, polyDataLike);
enum isAnyLegal = functionLike || dataLike || polyFunctionLike || polyDataLike;
enum isLegalCall = [ "objWithState" : objWithState,
"enumCheck" : enumCheck,
"calling": calling,
//can't be binary, seems dumb but will work fine.
"isAnyLegal" : isAnyLegal ? "true" : "false",
"polyFunctionLike": polyFunctionLike ? "true" : "false",
"polyDataLike" : polyDataLike ? "true" : "false",
"functionLike" : functionLike ? "true" : "false",
"dataLike" : dataLike ? "true" : "false"];
}
//funtion like, data like, polymorphed?
//args is used silently forwarded, which is a problem to be solved another time.
string ending(E)(string prefix, string funcCall, E fl, E dl, E pfl, E pdl) {
if (pfl) return prefix ~ funcCall ~ "(args);";
if (pdl) return prefix ~ funcCall ~ ";";
if (fl) return funcCall ~ "(args);";
if (dl) return funcCall ~ ";";
return ""; //asserting seems pointless here with the calling above.
}
/**expands to Enum declaration and aliased versions of all struct types.
must be inputted as a comma delimited string. no spaces or
extras. So "Base,SubRecord" would be two structs.
creates the background enum references as well as checks all the data types match
as no resizes/changes are allowed.*/
string expandTypes(string dataStruct, string prefix, string types) {
string structs;
string enums = "enum _prefix=\""~prefix~"\";enum _types=\""~types~"\";enum Enum {";
string sizeChecks;
int i; //cannot infer types?
foreach(str; splitter(types, ",")) {
//planned later, multiple inheretance like classes will work if related ones retain similar
//binary patterns. So say a struct with 111b, will let other struct functions of 11b or 1b as
//they retain the full patern minus the expanded parts. No way set that up at present.
//Idea: Use a new array with a split that has super->struct, this would have it append that type's
// value making the inhereited types work.
enums ~= str~"=(1<<"~(to!string(i))~")|1,";
structs ~= "alias "~str~" "~prefix~str~";";
sizeChecks ~= "static assert("~dataStruct~".sizeof=="~str~".sizeof, \""~str~": "
~"Polymorphic struct size doesn't match fixed size\");";
i++;
}
enums ~= "}";
return sizeChecks ~ structs ~ enums;
}
unittest {
string expected = `static assert(Data.sizeof==Base.sizeof, "Base: Polymorphic struct size doesn't match fixed size");static assert(Data.sizeof==SubRecord.sizeof, "SubRecord: Polymorphic struct size doesn't match fixed size");alias Base Poly_Base;alias SubRecord Poly_SubRecord;enum _prefix="Poly_";enum _types="Base,SubRecord";enum Enum {Base=(1<<0)|1,SubRecord=(1<<1)|1,}`;
string returned = expandTypes("Data", "Poly_", "Base,SubRecord");
assert(returned == expected, "\n"~expected~"\n"~returned);
}
string expandImports(string importString) {
string txt;
if (importString)
foreach(str; splitter(importString, ","))
txt ~= "import "~str~";";
return txt;
}
///Basic template added to inhereted/behavior structs. When using this, make and use no other variables
mixin template PolyMorphicInclude(PT) {
PT polyBase;
alias polyBase this;
// @disable this();
}
///To add opCmp that is polymorphic (and not all encompassing in the polymorph base) then include
///this after makePolyMorphicCall
mixin template PolyMorphicCompare(PT) {
///if not, forward reference to Poly_opCmp
int opCmp(ref const PT rhs) const {
return this.opDispatch!("opCmp")(rhs);
}
}
//checks for potential matches and selects the best one. Checks for const/mutable signature breaks,
//and builds compatct enum checks. If there's only one possibility, only that is returned instead.
string makePolyMorphicCall(string imports, string VariableAccess, string TypeCheck,
string prefix, string funCall, Enum, MutableState state, Vars ...)() {
string calls;
string results;
string firstSuccess;
int found;
//TODO: add some type of super/parent management for multiple levels.
foreach(e; EnumMembers!(Enum)) {
enum ilc = isLegalCall!(imports, VariableAccess, TypeCheck, prefix, funCall, to!string(e), state, Vars);
static if (ilc["isAnyLegal"] == "true") {
debug {
results ~= "\n// objWithState " ~ ilc["objWithState"];
results ~= "\t pfl = " ~ ilc["polyFunctionLike"];
results ~= "\t fl = " ~ ilc["functionLike"];
results ~= "\t pdl = " ~ ilc["polyDataLike"];
results ~= "\t dl = " ~ ilc["dataLike"];
results ~= "\t enum= " ~ ilc["enumCheck"];
results ~= "\t call= " ~ ilc["calling"];
results ~= "\n";
}
found++;
firstSuccess = ilc["calling"];
calls = ilc["enumCheck"] ~ ilc["calling"] ~ calls;
}
}
if (!calls.length) {
assert(0, "//Cannot identify function "~funCall~" or "~prefix~funCall);
} else {
calls ~= "assert(0, \"Failed to find appropriate funciton call for "~funCall~" or "~prefix~funCall~" during runtime.\");";
}
if (found == 1)
calls = firstSuccess; //remove enum check.
return results ~ calls;
}
/+
/*
*/
mixin template PolyMorphicIsLegalCast(PT)
if (hasMember!(PT, "Enum")) {
/**since i am not sure of how to do all casting checks
nor without breaking code, legal checks is likely safer.
This is only for basic type, no const or anything (would that even transfer?)
This will likely be redone to remove excess checks/runtime later
*/
bool isLegalCast(T)() const @property
if (is(T == PT)) {
return true;
}
bool isLegalCast(T)() const @property
if (!is(T == PT) && hasMember!(Enum, to!string(T))) {
foreach(e; EnumMembers!(Enum)) {
static if (to!string(e) == T) {
//getPolyMorphState present in other template mixin.
if ((getPolyMorphState & e) == e)
return true;
} else
return false; //exact check found, at this point break out.
}
//just in case.. i doubt it ever reaches here.
return false;
}
}
+/
/**The polymorphic header for the base.
PT is the PolymorphicType,
DT is your Data struct (including enum for type)
Variable access is what you initialize it using: ie Data data;
TypeCheck is where your enum check is located. Ie: struct Data { Enum polyMorphType}, being 'polyMorphType'
Adds opDispatch calls, getPolyMorphState/setPolyMorphState, and factory (only for these known subrecords)
*/
mixin template PolyMorphic(string imports, PT, DT, string VariableAccess, string TypeCheck = "polyMorphType")
if (hasMember!(PT, "Enum") && hasMember!(PT, "_prefix") && hasMember!(PT, "_types") &&
hasMember!(DT, TypeCheck) && hasMember!(PT, VariableAccess ~ "." ~ TypeCheck)) {
//mix-in-place add-ins
import std.traits;
import std.conv;
import std.stdio;
//if there are two levels of your prefix (say Poly_Poly_) then it's recursively
//trying and shouldn't be allowed any further. Not listed as part of the API as it's 'magic'
auto ref opDispatch(string fun, Args ...)(auto ref Args args) @property
if (fun.length <= _prefix.length*2 || fun[0 .. (_prefix.length*2)] != _prefix~_prefix) {
enum x = makePolyMorphicCall!(imports, VariableAccess, TypeCheck, _prefix, fun, Enum, MutableState.isMutable, args)();
version(Debug) { //lots of output with lots of calls. You have been warned!!
writeln(to!string(data.polyMorphType));
writeln(fun);
writeln(x);
}
mixin(x);
}
auto ref opDispatch(string fun, Args ...)(auto ref Args args) @property const
if (fun.length <= _prefix.length*2 || fun[0 .. (_prefix.length*2)] != _prefix~_prefix) {
enum x = makePolyMorphicCall!(imports, VariableAccess, TypeCheck, _prefix, fun, Enum, MutableState.isConst, args)();
version(Debug) {
writeln(to!string(data.polyMorphType));
writeln(fun);
writeln(x);
}
mixin(x);
}
auto ref opDispatch(string fun, Args ...)(auto ref Args args) @property immutable
if (fun.length <= _prefix.length*2 || fun[0 .. (_prefix.length*2)] != _prefix~_prefix) {
enum x = makePolyMorphicCall!(imports, VariableAccess, TypeCheck, _prefix, fun, Enum, MutableState.isImmutable, args)();
version(Debug) {
writeln(to!string(data.polyMorphType));
writeln(fun);
writeln(x);
}
mixin(x);
}
///set the current polymorphic state.
void setPolyMorphState(Enum type) @property @safe nothrow{
mixin(VariableAccess~"."~TypeCheck~" = type;");
}
///set the current polymorphic state.
void setPolyMorphState(string type) @property {
foreach(e; EnumMembers!(Enum)) {
if (to!string(e) == type) {
mixin(VariableAccess~"."~TypeCheck~" = e;");
break;
}
}
}
///Get current state/behavior.
Enum getPolyMorphState() const @property @safe nothrow {
mixin("return "~VariableAccess~"."~TypeCheck~";");
}
/+ //can't compile right now.
//isLegalCast moved to mixin template, but not working either.
///currently cannot do without fully re-implimenting alternate opAssign, opCmp. How annoying.
///returns if the type is equal (or accessed by inheretice)
///or that's the idea.
bool opEquals(Enum type) const {
mixin("return ("~VariableAccess~"."~TypeCheck~" & type) == type;");
}
///set type via normal assignment
// ref PT opAssign(E)(E type)
// if (is(E == Enum)) {
ref PT opAssign(Enum type) {
this.setPolyMorphState(type);
return this;
}
+/
///factory-like function. Not sure about filename references so only struct name is used.
static PT factory(string structName) {
PT tmp;
foreach(e; EnumMembers!(Enum)) {
enum str = to!string(e);
if (structName == str) {
tmp.setPolyMorphState(e);
break;
}
}
return tmp;
}
}