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