1 /**
2  * This module provides conversion functionality of different elements
3  * to the D Programming Language.
4  */
5 module dprotobuf.generator.dlang;
6 
7 import dprotobuf.pbroot;
8 import dprotobuf.pbgeneral;
9 import dprotobuf.pbchild;
10 import dprotobuf.pbenum;
11 import dprotobuf.pbmessage;
12 import dprotobuf.wireformat;
13 import codebuilder.structure;
14 
15 version(D_Version2) {
16 	import std.algorithm;
17 	import std.range;
18 	import std.regex;
19 	mixin(`
20 	version(unittest) {
21 		PBMessage PBCompileTime(ParserData pbstring) {
22 			return PBMessage(pbstring);
23 		}
24 		PBEnum PBCTEnum(ParserData pbstring) {
25 			return PBEnum(pbstring);
26 		}
27 	}`);
28 }
29 
30 import std.conv;
31 import std..string : format;
32 
33 /*
34  * Appropriately wraps the type based on the option.
35  *
36  * Repeated types are arrays.
37  * All types are nullable
38  */
39 private string typeWrapper(PBChild child) {
40 	if(child.modifier == "repeated")
41 		return format("Nullable!(%s[]) ", toDType(child.type));
42 	else
43 		return format("Nullable!(%s) ", toDType(child.type));
44 }
45 
46 private enum extenPrefix = "extension_";
47 
48 string langD(PBRoot root, bool markMessagesAsStatic = false) {
49 	auto code = CodeBuilder(0);
50     code.put("import dprotobuf.wireformat;\n");
51     code.put("import std.conv;\n");
52     code.put("import std.typecons;\n\n");
53 
54     code.put("string makeString(T)(T v) {\n");
55     code.put("\treturn to!string(v);\n");
56     code.put("}\n");
57 
58     // do what we need for extensions defined here
59     code.put(root.extensions.genExtString(""));
60     // write out enums
61     foreach(pbenum; root.enum_defs) {
62         code.put(toD(pbenum, 0));
63     }
64     // write out message definitions
65     foreach(pbmsg; root.message_defs) {
66         code.put(toD(pbmsg, 0, markMessagesAsStatic));
67     }
68     return code.finalize();
69 }
70 
71 /**
72  */
73 string toD(PBChild child, int indentCount = 0) {
74 	auto code = CodeBuilder(indentCount);
75 	with(child) {
76 		// Apply comments to field
77 		code.put(addComments(comments));
78 
79 		// Make field declaration
80 		if(is_dep) code.put("deprecated ref ");
81 		else code.put("");
82 		code.rawPut(typeWrapper(child));
83 		code.rawPut(name);
84 		if(isReserved(name))
85 			code.rawPut("_");
86 		if(is_dep) {
87 			code.rawPut("() {\n", Indent.open);
88 			code.push("}\n");
89 			code.put("return " ~ name ~ "_dep;\n");
90 			code.pop();
91 			code.put("private ");
92 			code.rawPut(typeWrapper(child));
93 			code.put(name ~ "_dep");
94 		}
95 		if(!empty(valdefault)) // Apply default value
96 			code.rawPut(" = " ~ valdefault);
97 		code.rawPut(";");
98 	}
99 	return code.finalize();
100 }
101 
102 unittest {
103 	PBChild child;
104 
105 	// Conversion for optional
106 	auto str = ParserData("optional HeaderBBox bbox = 1;");
107 	child = PBChild(str);
108 	string ans = "///\nNullable!(HeaderBBox) bbox;";
109 	assert(toD(child) == ans, toD(child));
110 
111 	// Conversion for repeated
112 	str = ParserData("repeated HeaderBBox bbox = 1;");
113 	child = PBChild(str);
114 	ans = "///\nNullable!(HeaderBBox[]) bbox;";
115 	assert(toD(child) == ans);
116 
117 	// Conversion for required
118 	str = ParserData("required int32 value = 1;");
119 	child = PBChild(str);
120 	ans = "///\nNullable!(int) value;";
121 	assert(toD(child) == ans);
122 
123 	// Conversion for default value
124 	str = ParserData("required int64 value = 1 [default=6]; ");
125 	child = PBChild(str);
126 	ans = "///\nNullable!(long) value = 6;";
127 	assert(toD(child) == ans);
128 
129 	// Conversion for default, negative, deprecated value
130 	str = ParserData("optional int64 value = 1 [default=-32,deprecated=true];");
131 	child = PBChild(str);
132 	ans = "///\ndeprecated ref Nullable!(long) value() {\n";
133 	ans ~= "\treturn value_dep;\n}\n";
134 	ans ~= "private Nullable!(long) value_dep = -32;";
135 	assert(toD(child) == ans);
136 
137 	// Conversion for commented, indented
138 	str = ParserData("optional HeaderBBox bbox = 1;");
139 	child = PBChild(str);
140 	child.comments ~= "// This is a comment";
141 	ans = "\t/// This is a comment\n\tNullable!(HeaderBBox) bbox;";
142 	assert(toD(child, 1) == ans);
143 }
144 
145 CodeBuilder genExtenCode(PBChild child, int indentCount) {
146 	auto code = CodeBuilder(indentCount);
147 	with(child) {
148 		auto extName = extenPrefix~name;
149 
150 		if(is_dep) code.put("deprecated ");
151 		else code.put("");
152 		code.rawPut(typeWrapper(child));
153 		code.rawPut(extName);
154 		if(!empty(valdefault)) // Apply default value
155 			code.rawPut(" = " ~ valdefault);
156 		code.rawPut(";\n");
157 
158 		return code;
159 	}
160 }
161 
162 private string constructMismatchException(string type, int indentCount) {
163 	auto code = CodeBuilder(indentCount);
164 	code.put("throw new Exception(\"Invalid wiretype \" ~\n");
165 	code.put("   to!(string)(wireType) ~\n");
166 	code.put("   \" for variable type "~type~"\");\n\n");
167 	return code.finalize();
168 }
169 
170 private string constructUndecided(PBChild child, int indentCount, CodeBuilder tname) {
171 	auto code = CodeBuilder(indentCount);
172 	// this covers enums and classes,
173 	// since enums are declared as classes
174 	// also, make sure we don't think we're root
175 	with(child) {
176 		code.put("static if (is("~type~" == struct)) {\n", Indent.open);
177       auto closeIf = CodeBuilder(1);
178 		closeIf.put("} else\n", Indent.close | Indent.open);
179 		closeIf.put("static assert(0,\n");
180 		closeIf.put("  \"Can't identify type `" ~ type ~ "`\");\n");
181 		closeIf.put(Indent.close);
182 		code.push(closeIf);
183 
184 		code.put("if(wireType != WireType.lenDelimited)\n", Indent.open);
185 		code.rawPut(constructMismatchException(type, code.indentCount));
186 		code.put(Indent.close);
187 
188 		// no need to worry about packedness here, since it can't be
189 		code.put(tname);
190 		code.rawPut(" "~type~"(input,false);\n");
191 		code.put("} else static if (is("~type~" == enum)) {\n",
192 		      Indent.close | Indent.open);
193 
194 		// worry about packedness here
195 		code.put("if (wireType == WireType.varint) {\n", Indent.open);
196 		closeIf = CodeBuilder(1);
197 		closeIf.put("} else\n", Indent.close | Indent.open);
198 		closeIf.rawPut(constructMismatchException(type, code.indentCount));
199 		closeIf.put(Indent.close);
200 		code.push(closeIf);
201 
202 		code.put(tname);
203 		code.rawPut(" cast("~toDType(type)~")\n");
204 		code.put("   fromVarint!(int)(input);\n");
205 		if (modifier == "repeated") {
206 			code.put("} else if (wireType == WireType.lenDelimited) {\n",
207 			       Indent.close | Indent.open);
208 			code.put(tname);
209 			code.rawPut("\n");
210 			code.put("   fromPacked!("~toDType(type)~
211 				",fromVarint!(int))(input);\n");
212 		}
213 	}
214 	return code.finalize();
215 }
216 
217 string genDes(PBChild child, int indentCount = 0, bool is_exten = false) {
218 	auto code = CodeBuilder(indentCount);
219 	with(child) {
220 		if(type == "group")
221 			throw new Exception("Group type not supported");
222 
223       auto tnameCode = CodeBuilder(0);
224 		string tname = name;
225 		if (is_exten) tname = extenPrefix~tname;
226 		if (is_dep) tname ~= "_dep";
227 		if(isReserved(tname)) {
228 			tname = tname ~ "_";
229 		}
230 		if (modifier == "repeated") {
231 			tnameCode.put("if(");
232 			tnameCode.rawPut(tname);
233 			tnameCode.rawPut(".isNull) ");
234 			tnameCode.rawPut(tname);
235 			tnameCode.rawPut(" = new "~toDType(type)~"[](0);\n");
236 			tnameCode.put(tname ~ " ~=");
237 		} else {
238 			tnameCode.put(tname);
239 			tnameCode.rawPut(" =");
240 		}
241 
242 		// check header ubyte with case since we're guaranteed to be in a switch
243 		code.put("case "~to!(string)(index)~":", Indent.open);
244 		code.push("break;\n");
245 
246 		code.rawPut("// Deserialize member ");
247 		code.rawPut(to!(string)(index));
248 		code.rawPut(" Field Name " ~ name ~ "\n");
249 
250 		// Class and Enum will have an undecided type
251 		if(wTFromType(type) == WireType.undecided) {
252 			code.rawPut(constructUndecided(child, code.indentCount, tnameCode));
253 			return code.finalize();
254 		}
255 
256 		// Handle Packed type
257 		if (packed) {
258 			assert(modifier == "repeated");
259 			assert(isPackable(type));
260 			// Allow reading data even when not packed
261 			code.put("if (wireType != WireType.lenDelimited)\n", Indent.open);
262 			code.push(Indent.close);
263 		}
264 
265 		// Verify wire type is expected type else
266 		// this is not condoned, wiretype is invalid, so explode!
267 		code.put("if (wireType != WireType." ~
268 			to!(string)(wTFromType(type))~")\n", Indent.open);
269 		code.rawPut(constructMismatchException(type, code.indentCount));
270 		code.put(Indent.close);
271 
272 		if (packed)
273 			code.pop();
274 
275 		string pack;
276 		switch(type) {
277 		case "float","double","sfixed32","sfixed64","fixed32","fixed64":
278 			pack = "fromByteBlob!("~toDType(type)~")";
279 			break;
280 		case "bool","int32","int64","uint32","uint64":
281 			pack = "fromVarint!("~toDType(type)~")";
282 			break;
283 		case "sint32","sint64":
284 			pack = "fromSInt!("~toDType(type)~")";
285 			break;
286 		case "string","bytes":
287 			// no need to worry about packedness here, since it can't be
288 			code.put(tnameCode);
289 			code.rawPut("\n");
290 			code.put("   fromByteString!("~toDType(type)~")(input);\n");
291 			return code.finalize();
292 		default:
293 			assert(0, "class/enum/group handled by undecided type.");
294 		}
295 
296 		if(packed) {
297 			code.put("if (wireType == WireType.lenDelimited) {\n", Indent.open);
298 			code.put(tnameCode);
299 			code.rawPut("\n");
300 			code.put("   fromPacked!("~toDType(type)~","~pack~")(input);\n");
301 			code.put("//Accept data even when not packed\n");
302 			code.put("} else {\n", Indent.close | Indent.open);
303 			code.push("}\n");
304 		}
305 
306 		code.put(tnameCode);
307 		code.rawPut(" ");
308 		code.rawPut(pack ~ "(input);\n");
309 		return code.finalize();
310 	}
311 }
312 
313 string genSer(PBChild child, int indentCount = 0, bool is_exten = false) {
314 	auto code = CodeBuilder(indentCount);
315 	with(child) {
316 		if(type == "group")
317 			throw new Exception("Group type not supported");
318 		code.put("// Serialize member ");
319 		code.rawPut(to!(string)(index));
320 		code.rawPut(" Field Name " ~ name ~ "\n");
321 
322 		string func;
323 		bool customType = false;
324 		switch(type) {
325 		case "float","double","sfixed32","sfixed64","fixed32","fixed64":
326 			func = "toByteBlob";
327 			break;
328 		case "bool","int32","int64","uint32","uint64":
329 			func = "toVarint";
330 			break;
331 		case "sint32","sint64":
332 			func = "toSInt";
333 			break;
334 		case "string","bytes":
335 			// the checks ensure that these can never be packed
336 			func = "toByteString";
337 			break;
338 		default:
339 			// this covers defined messages and enums
340 			func = "toVarint";
341 			customType = true;
342 			break;
343 		}
344 		auto tname = (is_exten?extenPrefix:"") ~ name;
345 
346 		if(is_dep) tname ~= "_dep";
347 		if(isReserved(tname))
348 			tname ~= "_";
349 
350 		if (modifier == "repeated" && !packed) {
351 			code.put("if(!"~tname~".isNull)\n");
352 			code.put("foreach(iter;");
353 			code.rawPut(tname);
354 			code.rawPut(".get()) {\n", Indent.open);
355 			code.push("}\n");
356          tname = "iter";
357 		}
358 
359 		// we have to have some specialized code to deal with enums vs
360 		// user-defined classes, since they are both detected the same
361 		if (customType) {
362 			code.put("static if (is("~type~" == struct)) {\n", Indent.open);
363 			auto closeIf = CodeBuilder(1);
364 			closeIf.put("} else\n", Indent.close | Indent.open);
365 			closeIf.put("static assert(0,\"Can't identify type `");
366 			closeIf.rawPut(type ~ "`\");\n");
367 			closeIf.put(Indent.close);
368 			code.push(closeIf);
369 			// packed only works for primitive types, so take care of normal
370 			// repeated serialization here since we can't easily detect this
371 			// without decent type resolution in the .proto parser
372 			if (packed) {
373 				assert(modifier == "repeated");
374 				code.put("foreach(iter;");
375 				code.rawPut(tname);
376 				code.put(") {\n", Indent.open);
377 				code.push("}\n");
378 			} else if (modifier != "required" && modifier != "repeated") {
379 				code.put("if (!");
380 				code.rawPut(tname);
381 				code.rawPut(".isNull) ");
382 			} else
383 				code.put(""); // Adds indenting
384 
385 			code.rawPut("ret ~= ");
386 			if(packed)
387 				code.rawPut("iter");
388 			else
389 				code.rawPut(tname);
390 			code.rawPut(".Serialize("~to!(string)(index)~");\n");
391 
392 			if (packed)
393 				code.pop();
394 			// done taking care of unpackable classes
395 			code.put("} else static if (is("~type~" == enum)) {\n",
396 			         Indent.close | Indent.open);
397 		}
398 		// take care of packed circumstances
399 		if (packed) {
400 			assert(modifier == "repeated");
401 			auto packType = toDType(type);
402 			if(customType)
403 				packType = "int";
404 			code.put("if(!");
405 			code.rawPut(tname);
406 			code.rawPut(".isNull)\n", Indent.open);
407 			code.put("ret ~= toPacked!("~packType~"[],"~func~")");
408 			code.put(Indent.close);
409 		} else if (modifier != "required" && modifier != "repeated") {
410 			code.put("if (!");
411 			code.rawPut(tname);
412 			code.rawPut(".isNull) ");
413 		} else
414 			code.put(""); // Adds indenting
415 
416 		if(!packed)
417 			code.rawPut("ret ~= "~func);
418 		// finish off the parameters, because they're the same for packed or not
419 		// finish off the parameters, because they're the same for packed or not
420 		if(customType) {
421 			if(packed)
422 				code.rawPut("(cast(int[])");
423 			else
424 				code.rawPut("(cast(int)");
425 		} else
426 			code.rawPut("(");
427 
428 		if(modifier != "repeated" && !packed)
429 			code.rawPush(".get(),", Indent.none);
430 		else
431 			code.rawPush(",", Indent.none);
432 
433 		code.rawPut(tname);
434 		code.pop();
435 		code.rawPut(to!(string)(index)~");\n");
436 
437 	}
438 	return code.finalize();
439 }
440 
441 version(D_Version2)
442 unittest {
443 	PBChild child;
444 
445 	// Optional messages don't ser if missing issue #27
446 	auto str = ParserData("optional SomeMessage sm = 2;");
447 	child = PBChild(str);
448 	mixin(`enum one = ParserData("message AnyLineDescriptor {
449                                  optional SomeMessage m2 = 2; }");`);
450 	mixin(`enum two = ParserData("message SomeMessage {}");`);
451 	mixin(`enum ichi = PBCompileTime(one);`);
452 	mixin(`enum ni = PBCompileTime(two);`);
453 	mixin(`import dprotobuf.wireformat;`);
454 	mixin(`import std.typecons;`);
455 	mixin("static " ~ ni.toD);
456 	mixin("static " ~ ichi.toD);
457 	AnyLineDescriptor ald;
458 	ald.Serialize();
459 }
460 
461 /**
462  */
463 string toD(PBEnum child, int indentCount = 0) {
464 	auto code = CodeBuilder(indentCount);
465 	with(child) {
466 		// Apply comments to enum
467 		code.put(addComments(comments));
468 
469 		code.put("enum "~name~" {\n", Indent.open);
470 		code.push("}\n");
471 		foreach (key, value; values) {
472 			// Apply comments to field
473 			if(key in valueComments)
474 				code.put(addComments(valueComments[key]));
475 			else
476 				code.put("///\n");
477 
478 			code.put(value~" = "~to!(string)(key)~",\n");
479 		}
480 	}
481 	return code.finalize();
482 }
483 
484 version(D_Version2)
485 unittest {
486 	mixin(`enum str = ParserData("enum potato
487                                  {TOTALS = 1;JUNK= 5 ; ALL =3;}");`);
488 	mixin(`enum enm = PBCTEnum(str);`);
489 	mixin(`import dprotobuf.wireformat;`);
490 	mixin(`import std.typecons;`);
491 	mixin(enm.toD);
492 	assert(potato.TOTALS == 1);
493 	assert(potato.JUNK == 5);
494 	assert(potato.ALL == 3);
495 
496 	// Conversion for commented, indented
497 	auto str2 = ParserData("enum potato{
498                            // The total
499                            // Set to 1
500                            TOTALS = 1;}");
501 	auto enm2 = PBEnum(str2);
502 	enm2.comments ~= "// My food";
503 	auto ans = regex(r"\t/// My food\n"
504 r"\tenum potato \{\n" ~
505 r"\t\t/// The total\n" ~
506 r"\t\t/// Set to 1\n" ~
507 r"\t\tTOTALS = \d,\n" ~
508 r"\t\}");
509 	assert(!enm2.toD(1).match(ans).empty, enm2.toD(1));
510 }
511 
512 string genDes(PBMessage msg, int indentCount = 0) {
513 	auto code = CodeBuilder(indentCount);
514 	string ret = "";
515 	with(msg) {
516 		// add comments
517 		code.put("// if we're root, we can assume we own the whole string\n");
518 		code.put("// if not, the first thing we need to do is pull the length that belongs to us\n");
519 		code.put("static "~name~" Deserialize(ubyte[] manip) {\n", Indent.open);
520 		code.put("return "~name~"(manip,true);\n");
521 		code.put("}\n", Indent.close);
522 		code.put("this(ubyte[] manip,bool isroot=true) {\n", Indent.open);
523 		code.put("this(manip,isroot);\n");
524 		code.put("}\n", Indent.close);
525 		code.put("this(ref ubyte[] manip,bool isroot=true) {\n", Indent.open);
526 		code.push("}\n");
527 		code.put("ubyte[] input = manip;\n");
528 
529 		code.put("// cut apart the input string\n");
530 		code.put("if (!isroot) {\n", Indent.open);
531 		code.push("}\n");
532 		code.put("uint len = fromVarint!(uint)(manip);\n");
533 		code.put("input = manip[0..len];\n");
534 		code.put("manip = manip[len..$];\n");
535 		code.pop();
536 
537 		// deserialization code goes here
538 		code.put("while(input.length) {\n", Indent.open);
539 		code.push("}\n");
540 		code.put("int header = fromVarint!(int)(input);\n");
541 		code.put("auto wireType = getWireType(header);\n");
542 		code.put("switch(getFieldNumber(header)) {\n");
543 		code.push("}\n");
544 		//here goes the meat, handily, it is generated in the children
545 		foreach(pbchild;children) {
546 			code.rawPut(genDes(pbchild, code.indentCount));
547 		}
548 		foreach(pbchild;child_exten) {
549 			code.rawPut(genDes(pbchild, code.indentCount, true));
550 		}
551 		// take care of default case
552 		code.put("default:\n", Indent.open);
553 		code.push("break;\n");
554 		code.put("	// rip off unknown fields\n");
555 		code.put("if(input.length)\n", Indent.open);
556 		code.put("ufields ~= toVarint(header)~\n");
557 		code.put("   ripUField(input,getWireType(header));\n");
558 		code.pop();
559 		code.pop();
560 		code.pop();
561 
562 		// check for required fields
563 		foreach(pbchild;child_exten) if (pbchild.modifier == "required") {
564 			code.put("if ("~extenPrefix~pbchild.name~".isNull)\n", Indent.open);
565 			code.put("throw new Exception(\"Did not find a "~pbchild.name~" in the message parse.\");\n");
566 			code.put(Indent.close);
567 		}
568 		foreach(pbchild;children) if (pbchild.modifier == "required") {
569 			auto field = pbchild.name;
570 			if(isReserved(field))
571 				field = field ~ "_";
572 			code.put("if ("~field~".isNull) throw new Exception(\"Did not find a "~field~" in the message parse.\");\n");
573 		}
574 		return code.finalize();
575 	}
576 }
577 
578 string genSer(PBMessage msg, int indentCount = 0) {
579 	auto indent = indented(indentCount);
580 	string ret = "";
581 	with(msg) {
582 		// use -1 as a default value, since a nibble can not produce that number
583 		ret ~= indent~"ubyte[] Serialize(int field = -1) const {\n";
584 		indent = indented(++indentCount);
585 		// codegen is fun!
586 		ret ~= indent~"ubyte[] ret;\n";
587 		// serialization code goes here
588 		foreach(pbchild;children) {
589 			ret ~= genSer(pbchild, indentCount);
590 		}
591 		foreach(pbchild;child_exten) {
592 			ret ~= genSer(pbchild, indentCount,true);
593 		}
594 		// tack on unknown bytes
595 		ret ~= indent~"ret ~= ufields;\n";
596 
597 		// include code to determine if we need to add a tag and a length
598 		ret ~= indent~"// take care of header and length generation if necessary\n";
599 		ret ~= indent~"if (field != -1) {\n";
600 		// take care of length calculation and integration of header and length
601 		ret ~= indented(indentCount+1)~"ret = genHeader(field,WireType.lenDelimited)~toVarint(ret.length,field)[1..$]~ret;\n";
602 		ret ~= indent~"}\n";
603 
604 		ret ~= indent~"return ret;\n";
605 		indent = indented(--indentCount);
606 		ret ~= indent~"}\n";
607 	}
608 	return ret;
609 }
610 string genMerge(PBMessage msg, int indentCount = 0) {
611 	auto indent = indented(indentCount);
612 	string ret = "";
613 	with(msg) {
614 		ret ~= indent~"void MergeFrom("~name~" merger) {\n";
615 		indent = indented(++indentCount);
616 		// merge code
617 		foreach(pbchild;children) {
618 			auto field = pbchild.name;
619 			if(pbchild.is_dep) field ~= "_dep";
620 			if(isReserved(field))
621 				field = field ~ "_";
622 			if (pbchild.modifier != "repeated") {
623 			ret ~= indent~"if (!merger."~field~".isNull) "~
624 				field~" = merger."~field~";\n";
625 			} else {
626 				ret ~= indent~"if (!merger."~field~".isNull) "~
627 					field~" ~= merger."~field~";\n";
628 			}
629 		}
630 		indent = indented(--indentCount);
631 		ret ~= indent~"}\n";
632 		return ret;
633 	}
634 }
635 
636 /**
637  */
638 string toD(PBMessage msg, int indentCount = 0, bool staticChild = false) {
639 	auto indent = indented(indentCount);
640 	string ret = "";
641 	with(msg) {
642 		ret ~= addComments(comments).finalize();
643 
644 		ret ~= indent~(staticChild?"static ":"")~"struct "~name~" {\n";
645 		indent = indented(++indentCount);
646 		ret ~= indent~"// deal with unknown fields\n";
647 		ret ~= indent~"ubyte[] ufields;\n";
648 		// fill the class with goodies!
649 		// first, we'll do the enums!
650 		foreach(pbenum;enum_defs) {
651 			ret ~= toD(pbenum, indentCount);
652 			ret ~= "\n\n";
653 		}
654 		// now, we'll do the nested messages
655 		foreach(pbmsg;message_defs) {
656 			ret ~= toD(pbmsg, indentCount, true);
657 			ret ~= "\n\n";
658 		}
659 		// do the individual instantiations
660 		foreach(pbchild;children) {
661 			ret ~= toD(pbchild, indentCount);
662 			ret ~= "\n";
663 		}
664 		// last, do the extension instantiations
665 		foreach(pbchild;child_exten) {
666 			ret ~= genExtenCode(pbchild, indentCount).finalize;
667 			ret ~= "\n";
668 		}
669 		ret ~= "\n";
670 		// here is where we add the code to serialize and deserialize
671 		ret ~= genSer(msg, indentCount);
672 		ret ~= "\n";
673 		ret ~= genDes(msg, indentCount);
674 		ret ~= "\n";
675 		// define merging function
676 		ret ~= genMerge(msg, indentCount);
677 		ret ~= "\n";
678 		// deal with what little we need to do for extensions
679 		ret ~= extensions.genExtString(indent~"static ");
680 
681 		// guaranteed to work, since we tack on a tab earlier
682 		indent = indented(--indentCount);
683 		ret ~= indent~"}\n";
684 	}
685 	return ret;
686 }
687 
688 bool isReserved(string field) {
689 	string[] words = [
690 "Error", "Exception", "Object", "Throwable", "__argTypes", "__ctfe",
691 	"__gshared", "__monitor", "__overloadset", "__simd", "__traits",
692 	"__vector", "__vptr", "_argptr", "_arguments", "_ctor", "_dtor",
693 	"abstract", "alias", "align", "assert", "auto", "body", "bool", "break",
694 	"byte", "cast", "catch", "cdouble", "cent", "cfloat", "char", "class",
695 	"const", "contained", "continue", "creal", "dchar", "debug", "delegate",
696 	"delete", "deprecated", "do", "double", "dstring", "else", "enum",
697 	"export", "extern", "false", "final", "finally", "float", "float", "for",
698 	"foreach", "foreach_reverse", "function", "goto", "idouble", "if",
699 	"ifloat", "immutable", "import", "in", "in", "inout", "int", "int",
700 	"interface", "invariant", "ireal", "is", "lazy", "lazy", "long", "long",
701 	"macro", "mixin", "module", "new", "nothrow", "null", "out", "out",
702 	"override", "package", "pragma", "private", "protected", "public", "pure",
703 	"real", "ref", "return", "scope", "shared", "short", "static", "string",
704 	"struct", "super", "switch", "synchronized", "template", "this", "throw",
705 	"true", "try", "typedef", "typeid", "typeof", "ubyte", "ucent", "uint",
706 	"uint", "ulong", "ulong", "union", "unittest", "ushort", "ushort",
707 	"version", "void", "volatile", "wchar", "while", "with", "wstring"];
708 
709 	foreach(string w; words)
710 		if(w == field)
711 			return true;
712 	return false;
713 }
714 
715 CodeBuilder addComments(string[] comments) {
716 	auto code = CodeBuilder(0);
717 	foreach(c; comments) {
718 		if(c.length > 1)
719 			code.put((c[0..2] == "//") ? "/":"");
720 		else
721 			code.put("");
722 		code.rawPut(c~"\n");
723 	}
724 	if(comments.empty())
725 		code.put("///\n");
726 	return code;
727 }
728 
729 unittest {
730 	assert(isReserved("version"));
731 }
732 
733 version(D_Version2)
734 unittest {
735 	// Conversion for optional
736 	mixin(`enum str = ParserData("message Test1 { required int32 a = 1; }");`);
737 	mixin(`enum msg = PBCompileTime(str);`);
738 	mixin(`import dprotobuf.wireformat;`);
739 	mixin(`import std.typecons;`);
740 	mixin("static " ~ msg.toD);
741 	ubyte[] feed = [0x08,0x96,0x01]; // From example
742 	auto t1 = Test1(feed);
743 	assert(t1.a == 150);
744 	assert(t1.Serialize() == feed);
745 }
746 
747 version(D_Version2)
748 unittest {
749 	auto str = ParserData("optional OtherType type = 1;");
750 	auto ms = PBChild(str);
751 	toD(ms);
752 }
753 
754 version(D_Version2)
755 unittest {
756 	// Conversion for repated packed
757 	mixin(`enum str = ParserData("message Test4 {
758 	                              repeated int32 d = 4 [packed=true]; }");`);
759 	mixin(`enum msg = PBCompileTime(str);`);
760 	mixin(`import dprotobuf.wireformat;`);
761 	mixin(`import std.typecons;`);
762 	mixin("static " ~ msg.toD);
763 	ubyte[] feed = [0x22, // Tag (field number 4, wire type 2)
764 		0x06, // payload size (6 bytes)
765 		0x03, // first element (varint 3)
766 		0x8E,0x02, // second element (varint 270)
767 		0x9E,0xA7,0x05 // third element (varint 86942)
768 			]; // From example
769 	auto t4 = Test4(feed);
770 	assert(t4.d == [3,270,86942]);
771 	assert(t4.Serialize() == feed);
772 }
773 
774 version(D_Version2)
775 unittest {
776 	// Conversion for string
777 	mixin(`enum str = ParserData("message Test2 {
778 	                              required string b = 2; }");`);
779 	mixin(`enum msg = PBCompileTime(str);`);
780 	mixin(`import dprotobuf.wireformat;`);
781 	mixin(`import std.typecons;`);
782 	mixin("static " ~ msg.toD);
783 	ubyte[] feed = [0x12,0x07, // (tag 2, type 2) (length 7)
784 		0x74,0x65,0x73,0x74,0x69,0x6e,0x67
785 			]; // From example
786 	auto t2 = Test2(feed[]);
787 	assert(t2.b == "testing");
788 	assert(t2.Serialize() == feed);
789 }
790 
791 version(D_Version2)
792 unittest {
793 	// Tests parsing does not pass message
794 	mixin(`enum str = ParserData("message Test2 {
795 	                              repeated string b = 2;
796 	                              repeated string c = 3; }");`);
797 	mixin(`enum msg = PBCompileTime(str);`);
798 	mixin(`import dprotobuf.wireformat;`);
799 	mixin(`import std.typecons;`);
800 	mixin("static " ~ msg.toD);
801 	ubyte[] feed = [0x09,(2<<3) | 2,0x07,
802 		0x74,0x65,0x73,0x74,0x69,0x6e,0x67,
803 		3<<3 | 0,0x08
804 			];
805 	auto feedans = feed;
806 	auto t2 = Test2(feed, false);
807 	assert(t2.b == ["testing"]);
808 	assert(t2.Serialize() == feedans[1..$-2]);
809 }
810 
811 version(D_Version2)
812 unittest {
813 	// Packed enum data
814 	mixin(`enum um = ParserData("enum MyNum {
815 	                              YES = 1; NO = 2; }");`);
816 	mixin(`enum str = ParserData("message Test {
817 	                              repeated MyNum b = 2 [packed=true]; }");`);
818 	mixin(`enum msg = PBCompileTime(str);`);
819 	mixin(`enum yum = PBCTEnum(um);`);
820 	mixin(`import dprotobuf.wireformat;`);
821 	mixin(`import std.typecons;`);
822 	mixin(yum.toD);
823 	mixin("static " ~ msg.toD);
824 	ubyte[] feed = [(2<<3) | 2,0x02,
825 		0x01,0x02
826 			];
827 	auto t = Test(feed);
828 	assert(t.b == [MyNum.YES, MyNum.NO]);
829 	assert(t.Serialize() == feed);
830 }
831 
832 version(D_Version2)
833 unittest {
834 	// Type Generation
835 	mixin(`enum one = ParserData("enum Settings {
836 	                              FOO = 1;
837 	                              BAR = 2;
838 	                          }");`);
839 	mixin(`enum two = ParserData("message Type {
840 	                              repeated int32 data = 1;
841 	                              repeated int32 extra = 2 [packed = true];
842 	                              optional int32 last = 3;
843 	                          }");`);
844 	mixin(`enum three = ParserData("message OtherType {
845 	                              optional Type struct = 1;
846 	                              repeated Settings enum = 2 [packed = true];
847 	                              repeated Settings num = 3;
848 	                              repeated string a = 4;
849 	                              required string b = 5;
850 	                              repeated Type t = 6;
851 	                          }");`);
852 	mixin(`enum ichi = PBCTEnum(one);`);
853 	mixin(`enum ni = PBCompileTime(two);`);
854 	mixin(`enum san = PBCompileTime(three);`);
855 	mixin(`import dprotobuf.wireformat;`);
856 	mixin(`import std.typecons;`);
857 	mixin(ichi.toD);
858 	mixin("static " ~ ni.toD);
859 	mixin("static " ~ san.toD);
860 	ubyte[] feed = [((1 << 3) | 2), 8, // OtherType.Type
861 	((1 << 3) | 0), 1, ((1 << 3) | 0), 2, // Type.Data
862 	((2 << 3) | 2), 2, 3, 4, // Type.Extra
863 
864 	((2 << 3) | 2), 1, 1, // OtherType.enum
865 	((3 << 3) | 0), 2, // OtherType.num
866 	((4 << 3) | 2), 1, 'a', // OtherType.a
867 	((4 << 3) | 2), 1, 'b', // OtherType.a
868 	((5 << 3) | 2), 2, 'c', 'd', // OtherType.b
869 
870 	((6 << 3) | 2), 4, // OtherType.Type
871 	((1 << 3) | 0), 2, // Type.Data
872 	((1 << 3) | 0), 2, // Type.Data
873 	((6 << 3) | 2), 2, // OtherType.Type
874 	((1 << 3) | 0), 3, // Type.Data
875 	];
876 	auto ot = OtherType(feed);
877 	assert(ot.struct_.data == [1, 2]);
878 	assert(ot.struct_.extra == [3, 4]);
879 	assert(ot.enum_ == [Settings.FOO]);
880 	assert(ot.num == [Settings.BAR]);
881 	assert(ot.a == ["a", "b"]);
882 	assert(ot.b == "cd");
883 	assert(ot.t[0].data == [2,2]);
884 	assert(ot.t[1].data == [3]);
885 	assert(ot.t.length == 2);
886 
887 	auto res = ot.Serialize();
888 	assert(res == feed);
889 }
890 
891 unittest {
892 	enum foo =
893 ParserData("message Foo {
894 	optional int32 de = 5;
895 	extensions 1 to 4;
896 }
897 extend Foo {
898 	optional int32 blah = 1;
899     repeated string da = 2;
900 }
901 ");
902 
903 	enum root = PBRoot(foo);
904 	enum ExtFoo = root.getExtensions();
905 
906 	mixin(applyExtensions(root, ExtFoo).langD(true));
907     Foo m;
908     m.extension_blah = 5;
909 }