1 /// module for serialisation/deserialisation for files or other uses
2 module ydlib.serialise;
3 
4 import std.string;
5 import std.bitmanip;
6 
7 /// type of a value (integer/string/structure)
8 enum ValueType {
9 	Integer   = 0x00,
10 	String    = 0x01,
11 	Structure = 0x02
12 }
13 
14 /// flags used for different variations of a value
15 enum Flags {
16 	Array = 0b00000001
17 }
18 
19 /// contents of a value
20 union ValueContents {
21 	int    _int;
22 	string _string;
23 
24 	this(int pint) {
25 		_int = pint;
26 	}
27 
28 	this(string pstring) {
29 		_string = pstring;
30 	}
31 }
32 
33 /// main value structure
34 struct Value {
35 	ValueType type;
36 	bool      isArray;
37 
38 	union {
39 		ValueContents   single;
40 		ValueContents[] array;
41 		Structure       structure;
42 	}
43 
44 	/// makes a value from an integer
45 	this(int from) {
46 		type     = ValueType.Integer;
47 		isArray  = false;
48 		single   = ValueContents(from);
49 	}
50 
51 	/// makes a value from a string
52 	this(string from) {
53 		type     = ValueType.String;
54 		isArray  = false;
55 		single   = ValueContents(from);
56 	}
57 
58 	/// makes a value from an integer array
59 	this(int[] from) {
60 		type    = ValueType.Integer;
61 		isArray = true;
62 
63 		foreach (ref val ; from) {
64 			array ~= ValueContents(val);
65 		}
66 	}
67 
68 	/// makes a value from a string array
69 	this(string[] from) {
70 		type    = ValueType.String;
71 		isArray = true;
72 
73 		foreach (ref val ; from) {
74 			array ~= ValueContents(val);
75 		}
76 	}
77 
78 	/// makes a value from a structure
79 	this(Structure from) {
80 		type      = ValueType.Structure;
81 		isArray   = false;
82 		structure = from;
83 	}
84 
85 	/// makes a value from an integer
86 	static Value Int(int from = 0) {
87 		return Value(from);
88 	}
89 
90 	/// makes a value from a string
91 	static Value String(string from = "") {
92 		return Value(from);
93 	}
94 
95 	/// makes a value from an integer array
96 	static Value IntArray(int[] from = []) {
97 		return Value(from);
98 	}
99 
100 	/// makes a value from a string array
101 	static Value StringArray(string[] from = []) {
102 		return Value(from);
103 	}
104 
105 	/// returns the value's contents as an integer
106 	int GetInt() {
107 		assert(type == ValueType.Integer);
108 		assert(!array);
109 
110 		return single._int;
111 	}
112 
113 	/// returns the value's contents as a string
114 	string GetString() {
115 		assert(type == ValueType.String);
116 		assert(!array);
117 
118 		return single._string;
119 	}
120 
121 	/// returns the value's contents as an integer array
122 	int[] GetIntArray() {
123 		assert(type == ValueType.Integer);
124 		assert(array);
125 
126 		int[] ret;
127 
128 		foreach (ref val ; array) {
129 			ret ~= val._int;
130 		}
131 
132 		return ret;
133 	}
134 
135 	/// returns the value's contents as a string array
136 	string[] GetStringArray() {
137 		assert(type == ValueType.String);
138 		assert(array);
139 
140 		string[] ret;
141 
142 		foreach (ref val ; array) {
143 			ret ~= val._string;
144 		}
145 
146 		return ret;
147 	}
148 }
149 
150 /// structures are just arrays of values
151 alias Structure = Value[];
152 
153 /// exception for DataManager methods
154 class DataException : Exception {
155 	this(string msg, string file = __FILE__, size_t line = __LINE__) {
156 		super(msg, file, line);
157 	}
158 }
159 
160 /// main class for data from a file etc
161 class DataManager {
162 	Structure[string] structures;
163 
164 	/// adds a structure type
165 	void AddStructure(string name, Structure structure) {
166 		structures[name] = structure;
167 	}
168 
169 	/// checks if a structure is valid
170 	bool ValidStructure(string name, Structure structure) {
171 		Structure type = structures[name];
172 
173 		if (structure.length != type.length) {
174 			return false;
175 		}
176 
177 		foreach (i, ref val ; structure) {
178 			if (val.type != type[i].type) {
179 				return false;
180 			}
181 		}
182 
183 		return true;
184 	}
185 
186 	/// turns the data into an array of bytes
187 	ubyte[] Serialise(Structure structure) {
188 		ubyte[] ret;
189 
190 		ret ~= nativeToBigEndian(cast(ulong) structure.length);
191 		
192 		foreach (ref val ; structure) {
193 			ret ~= cast(ubyte) val.type;
194 
195 			ubyte flags;
196 
197 			if (val.isArray) {
198 				flags |= cast(ubyte) Flags.Array;
199 			}
200 
201 			ret ~= flags;
202 			
203 			if (val.isArray) {
204 				ret ~= nativeToBigEndian(cast(ulong) val.array.length);
205 			}
206 		
207 			switch (val.type) {
208 				case ValueType.Integer: {
209 					if (val.isArray) {
210 						foreach (ref val2 ; val.array) {
211 							ret ~= nativeToBigEndian(val2._int);
212 						}
213 					}
214 					else {
215 						ret ~= nativeToBigEndian(val.single._int);
216 					}
217 					break;
218 				}
219 				case ValueType.String: {
220 					if (val.isArray) {
221 						foreach (ref val2 ; val.array) {
222 							ret ~= val2._string ~ 0;
223 						}
224 					}
225 					else {
226 						ret ~= val.single._string;
227 					}
228 					break;
229 				}
230 				case ValueType.Structure: {
231 					ret ~= Serialise(val.structure);
232 					break;
233 				}
234 				default: assert(0);
235 			}
236 		}
237 
238 		return ret;
239 	}
240 
241 	/// creates data from an array of bytes
242 	Structure Deserialise(ref ubyte[] data) {
243 		Structure ret;
244 
245 		ulong length = bigEndianToNative!ulong(data[0 .. 8]);
246 		data         = data[8 .. $];
247 
248 		while ((data.length > 0) && (ret.length < length)) {
249 			switch (cast(ValueType) data[0]) {
250 				case ValueType.Integer: {
251 					if (data[1] & Flags.Array) {
252 						ulong len = bigEndianToNative!ulong(data[2 .. 10]);
253 
254 						data = data[10 .. $];
255 						
256 						Value array;
257 						array.type    = ValueType.Integer;
258 						array.isArray = true;
259 
260 						for (ulong i = 0; i < len; ++i) {
261 							array.array ~= ValueContents(
262 								bigEndianToNative!int(data[0 .. 4])
263 							);
264 							data = data[4 .. $];
265 						}
266 
267 						ret ~= array;
268 					}
269 					else {
270 						ret  ~= Value(bigEndianToNative!int(data[1 .. 5]));
271 						data  = data[5 .. $];
272 					}
273 					break;
274 				}
275 				case ValueType.String: {
276 					string str;
277 
278 					data = data[1 .. $];
279 
280 					while (data[0] > 0) {
281 						str  ~= data[0];
282 						data  = data[1 .. $];
283 					}
284 
285 					data = data[1 .. $]; // remove null terminator
286 
287 					ret ~= Value(str);
288 					break;
289 				}
290 				case ValueType.Structure: {
291 					ret ~= Value(Deserialise(data));
292 					break;
293 				}
294 				default: assert(0);
295 			}
296 		}
297 
298 		return ret;
299 	}
300 }