1 module mutils.serializer.common;
2 
3 import std.algorithm : stripLeft;
4 import std.experimental.allocator;
5 import std.experimental.allocator.mallocator;
6 import std..string : indexOf;
7 import std.traits;
8 
9 import mutils.serializer.lexer_utils;
10 
11 /// Runtime check, throws nogc exception on error
12 static void check(string str = "Parsing Error")(bool ok) {
13 	enum hardCheck = false; // if true assert on error else throw Exception
14 	static if (hardCheck) {
15 		assert(ok, str);
16 	} else {
17 		shared static immutable Exception e = new Exception(str);
18 		if (!ok) {
19 			throw e;
20 		}
21 	}
22 }
23 
24 /// Enum to check if data is loaded or saved
25 enum Load {
26 	no = 0,
27 	yes = 1,
28 	skip = 2,
29 
30 }
31 
32 /// Checks if type have to be allocated by serializer
33 auto isMallocType(T)() {
34 	static if (isDynamicArray!T || isPointer!T || is(T == class)) {
35 		return true;
36 	} else {
37 		return false;
38 	}
39 }
40 
41 /// Checks if in user defined attributes(UDA) there is malloc string
42 /// "malloc" string UDA indicates that data should be allocated by serializer
43 auto hasMallocUda(Args...)() {
44 	bool hasMalloc = false;
45 	foreach (Arg; Args) {
46 		static if (is(typeof(Arg) == string) && Arg == "malloc") {
47 			hasMalloc = true;
48 		}
49 	}
50 	return hasMalloc;
51 }
52 
53 /// Checks if in user defined attributes(UDA) there is noserialize string
54 /// "noserialize" string UDA indicates that data should not be serialzied by serializer
55 auto hasNoserializeUda(Args...)() {
56 	bool hasMalloc = false;
57 	foreach (Arg; Args) {
58 		static if (is(typeof(Arg) == string) && Arg == "noserialize") {
59 			hasMalloc = true;
60 		}
61 	}
62 	return hasMalloc;
63 }
64 
65 bool isStringVector(T)() {
66 	static if (is(T == struct) && is(Unqual!(ForeachType!T) == char) && hasMember!(T, "opSlice")) {
67 		return true;
68 	} else {
69 		return false;
70 	}
71 }
72 /// Checks if type can be treated as vector ex. replace int[] with MyArray!int
73 bool isCustomVector(T)() {
74 	static if (is(T == struct) && hasMember!(T, "opOpAssign") && hasMember!(T,
75 			"add") && hasMember!(T, "length")) {
76 		return true;
77 	} else {
78 		return false;
79 	}
80 }
81 /// Checks if type can be treated as map
82 bool isCustomMap(T)() {
83 	static if (is(T == struct) && hasMember!(T, "byKey") && hasMember!(T,
84 			"byValue") && hasMember!(T, "byKeyValue") && hasMember!(T, "add")
85 			&& hasMember!(T, "isIn")) {
86 		return true;
87 	} else {
88 		return false;
89 	}
90 }
91 ///Returns Load.yes when load is Load.yes or Load.skip
92 Load loadOrSkip(Load load)() {
93 	static if (load == Load.yes || load == Load.skip) {
94 		return Load.yes;
95 	} else {
96 		return load;
97 
98 	}
99 }
100 
101 void commonSerialize(Load load, bool useMalloc = false, Serializer, T, COS)(
102 		Serializer ser, ref T var, ref COS con) {
103 	static if (__traits(compiles, var.beforeSerialize!(load)(ser, con))) {
104 		var.beforeSerialize!(load)(ser, con);
105 	}
106 
107 	static if (hasMember!(T, "customSerialize")) {
108 		var.customSerialize!(load)(ser, con);
109 	} else static if (isBasicType!T) {
110 		ser.serializeBasicVar!(load)(var, con);
111 	} else static if (isStringVector!T) {
112 		ser.serializeString!(load)(var, con);
113 	} else static if (isCustomVector!T) {
114 		ser.serializeCustomVector!(load)(var, con);
115 	} else static if (isCustomMap!T) {
116 		ser.serializeCustomMap!(load)(var, con);
117 	} else static if (is(T == struct)) {
118 		ser.serializeStruct!(load)(var, con);
119 	} else static if (isStaticArray!T) {
120 		ser.serializeStaticArray!(load)(var, con);
121 	} else static if (useMalloc && isMallocType!T) {
122 		static if (isDynamicArray!T) {
123 			ser.serializeDynamicArray!(load)(var, con);
124 		} else static if (is(T == class)) {
125 			ser.serializeClass!(load)(var, con);
126 		} else static if (isPointer!T) {
127 			ser.serializePointer!(load, useMalloc)(var, con);
128 		} else {
129 			static assert(0);
130 		}
131 	} else static if (!useMalloc && isMallocType!T) {
132 		//don't save, leave default value
133 	} else static if (is(T == interface)) {
134 		//don't save, leave default value
135 	} else {
136 		static assert(0, "Type can not be serialized");
137 	}
138 
139 	static if (hasMember!(T, "afterSerialize")) {
140 		var.afterSerialize!(load)(ser, con);
141 	}
142 }
143 
144 /// Struct to let BoundsChecking Without GC
145 struct NoGcSlice(T) {
146 	shared static immutable Exception e = new Exception("BoundsChecking NoGcException");
147 	T slice;
148 	alias slice this;
149 
150 	//prevent NoGcSlice in NoGcSlice, NoGcSlice!(NoGcSlice!(T))
151 	static if (!hasMember!(T, "slice")) {
152 		T opSlice(X, Y)(X start, Y end) {
153 			if (start >= slice.length || end > slice.length) {
154 				//assert(0);
155 				throw e;
156 			}
157 			return slice[start .. end];
158 		}
159 
160 		size_t opDollar() {
161 			return slice.length;
162 		}
163 	}
164 }
165 
166 void commonSerializePointer(Load load, bool useMalloc, Serializer, T, COS)(
167 		Serializer ser, ref T var, ref COS con) {
168 	static assert(isPointer!T);
169 	alias PointTo = typeof(*var);
170 	bool exists = var !is null;
171 	ser.serializeImpl!(loadOrSkip!load)(exists, con);
172 	if (!exists) {
173 		return;
174 	}
175 	static if (load == Load.yes) {
176 		if (var is null)
177 			var = Mallocator.instance.make!(PointTo);
178 	} else static if (load == Load.skip) {
179 		__gshared static PointTo helperObj;
180 		T beforeVar = var;
181 		if (var is null)
182 			var = &helperObj;
183 	}
184 	ser.serializeImpl!(load, useMalloc)(*var, con);
185 	static if (load == Load.skip) {
186 		var = beforeVar;
187 	}
188 
189 }
190 
191 void writelnTokens(TokenData[] tokens) {
192 	import mutils.stdio : writeln;
193 
194 	writeln("--------");
195 	foreach (tk; tokens) {
196 		writeln(tk);
197 	}
198 }
199 
200 void tokensToCharVectorPreatyPrint(Lexer, Vec)(TokenData[] tokens, ref Vec vec) {
201 	int level = 0;
202 	void addSpaces() {
203 		foreach (i; 0 .. level)
204 			vec ~= cast(char[]) "    ";
205 	}
206 
207 	foreach (tk; tokens) {
208 		if (tk.isChar('{')) {
209 			Lexer.toChars(tk, vec);
210 			level++;
211 			vec ~= '\n';
212 			addSpaces();
213 		} else if (tk.isChar('}')) {
214 			level--;
215 			vec ~= '\n';
216 			addSpaces();
217 			Lexer.toChars(tk, vec);
218 		} else if (tk.isChar(',')) {
219 			Lexer.toChars(tk, vec);
220 			vec ~= '\n';
221 			addSpaces();
222 		} else {
223 			Lexer.toChars(tk, vec);
224 		}
225 
226 	}
227 }
228 
229 //-----------------------------------------
230 //--- Helper methods for string format
231 //-----------------------------------------
232 
233 void serializeCustomVectorString(Load load, T, COS)(ref T var, ref COS con) {
234 	alias ElementType = Unqual!(ForeachType!(T));
235 	static assert(is(ElementType == char));
236 
237 	static if (load == Load.yes) {
238 		var = con[0].getUnescapedString;
239 		con = con[1 .. $];
240 	} else {
241 		TokenData token;
242 		token = cast(string) var[];
243 		token.type = StandardTokens.string_;
244 		con ~= token;
245 	}
246 }
247 
248 void serializeCharToken(Load load, COS)(char ch, ref COS con) {
249 	static if (load == Load.yes) {
250 		check(con[0].type == StandardTokens.character && con[0].isChar(ch));
251 		con = con[1 .. $];
252 	} else {
253 		TokenData token;
254 		token = ch;
255 		con ~= token;
256 	}
257 }
258 
259 void serializeBoolToken(Load load, COS)(ref bool var, ref COS con) {
260 	static if (load == Load.yes) {
261 		check(con[0].type == StandardTokens.identifier || con[0].type == StandardTokens.long_);
262 		if (con[0].type == StandardTokens.identifier) {
263 			if (con[0].str == "true") {
264 				var = true;
265 			} else if (con[0].str == "false") {
266 				var = false;
267 			} else {
268 				check(false);
269 			}
270 		} else {
271 			if (con[0].long_ == 0) {
272 				var = false;
273 			} else {
274 				var = true;
275 			}
276 		}
277 
278 		con = con[1 .. $];
279 	} else {
280 		TokenData token;
281 		token.type = StandardTokens.identifier;
282 		token.str = (var) ? "true" : "false";
283 		con ~= token;
284 	}
285 }
286 
287 void ignoreBraces(Load load, COS)(ref COS con, char braceStart, char braceEnd) {
288 	static assert(load == Load.yes);
289 	assert(con[0].isChar(braceStart));
290 	con = con[1 .. $];
291 	int nestageLevel = 1;
292 	while (con.length > 0) {
293 		TokenData token = con[0];
294 		con = con[1 .. $];
295 		if (token.type != StandardTokens.character) {
296 			continue;
297 		}
298 		if (token.isChar(braceStart)) {
299 			nestageLevel++;
300 		} else if (token.isChar(braceEnd)) {
301 			nestageLevel--;
302 			if (nestageLevel == 0) {
303 				break;
304 			}
305 		}
306 	}
307 }
308 
309 void ignoreToMatchingComma(Load load, COS)(ref COS con) {
310 	static assert(load == Load.yes);
311 	int nestageLevel = 0;
312 	//scope(exit)writelnTokens(con);
313 	while (con.length > 0) {
314 		TokenData token = con[0];
315 		if (token.type != StandardTokens.character) {
316 			con = con[1 .. $];
317 			continue;
318 		}
319 		if (token.isChar('[') || token.isChar('{')) {
320 			nestageLevel++;
321 		} else if (token.isChar(']') || token.isChar('}')) {
322 			nestageLevel--;
323 		}
324 
325 		if (nestageLevel < 0 || (nestageLevel == 0 && token.isChar(','))) {
326 			break;
327 		} else {
328 			con = con[1 .. $];
329 		}
330 	}
331 }