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 }