1 // this code implements the ability to extend messages 2 module dprotobuf.pbextension; 3 import dprotobuf.pbchild; 4 import dprotobuf.pbgeneral; 5 import std..string; 6 import std.stdio; 7 8 struct PBExtension { 9 string name; 10 PBChild[]children; 11 12 // string-modifying constructor 13 static PBExtension opCall(ref ParserData pbstring) 14 in { 15 assert(pbstring.length); 16 } body { 17 // things we currently support in a message: messages, enums, and children(repeated, required, optional) 18 // first things first, rip off "message" 19 pbstring = pbstring["extend".length..pbstring.length]; 20 // now rip off the next set of whitespace 21 pbstring = stripLWhite(pbstring); 22 // get message name 23 string name = stripValidChars(CClass.MultiIdentifier,pbstring); 24 PBExtension exten; 25 exten.name = name; 26 // rip off whitespace 27 pbstring = stripLWhite(pbstring); 28 // make sure the next character is the opening { 29 if (pbstring[0] != '{') { 30 throw new PBParseException("Message Definition","Expected next character to be '{'. You may have a space in your message name: "~name, pbstring.line); 31 } 32 // rip off opening { 33 pbstring = pbstring[1..pbstring.length]; 34 // prep for loop spinup by removing extraneous whitespace 35 pbstring = stripLWhite(pbstring); 36 // now we're ready to enter the loop and parse children 37 while(pbstring[0] != '}') { 38 // start parsing, we shouldn't have any whitespace here 39 exten.children ~= PBChild(pbstring); 40 // this needs to stay at the end 41 pbstring = stripLWhite(pbstring); 42 } 43 // rip off the } 44 pbstring = pbstring[1..pbstring.length]; 45 return exten; 46 } 47 } 48 49 unittest { 50 auto instr = 51 ParserData("extend Foo { 52 optional clunker blah = 1; 53 } 54 "); 55 writefln("unittest ProtocolBuffer.pbextension"); 56 auto exten = PBExtension(instr); 57 debug writefln("Checking PBExtension class correctness"); 58 assert(exten.name == "Foo","Class to be extended is incorrect"); 59 debug writefln("Checking PBExtension child correctness"); 60 assert(exten.children[0].name == "blah","Parsed child is incorrect"); 61 assert(exten.children[0].modifier == "optional","Parsed child is incorrect"); 62 assert(exten.children[0].type == "clunker","Parsed child is incorrect"); 63 assert(exten.children[0].index == 1,"Parsed child is incorrect"); 64 debug writefln(""); 65 } 66 67 import dprotobuf.pbmessage; 68 auto insertExtension(PBMessage pbmsg, PBExtension ext) { 69 assert(pbmsg.name == ext.name, "Extensions apply to a specific message; " ~ pbmsg.name ~ " != " ~ ext.name); 70 import std.conv; 71 foreach(echild;ext.children) { 72 bool extmatch = false; 73 foreach(exten;pbmsg.exten_sets) { 74 if (echild.index <= exten.max && echild.index >= exten.min) { 75 extmatch = true; 76 break; 77 } 78 } 79 if (!extmatch) throw new Exception("The field number "~to!(string)(echild.index)~" for extension "~echild.name~" is not within a valid extension range for "~pbmsg.name); 80 } 81 82 // now check each child vs each extension already applied to see if there are conflicts 83 foreach(dchild; pbmsg.child_exten) foreach(echild; ext.children) { 84 if (dchild.index == echild.index) throw new Exception("Extensions "~dchild.name~" and "~echild.name~" to "~pbmsg.name~" have identical index number "~to!(string)(dchild.index)); 85 } 86 pbmsg.child_exten ~= ext.children; 87 88 return pbmsg; 89 } 90 91 unittest { 92 auto foo = 93 ParserData("message Foo { 94 optional int de = 5; 95 extensions 1 to 4; 96 } 97 "); 98 auto extFoo = 99 ParserData("extend Foo { 100 optional int blah = 1; 101 } 102 "); 103 104 auto Foo = PBMessage(foo); 105 auto ExtFoo = PBExtension(extFoo); 106 107 auto m = insertExtension(Foo, ExtFoo); 108 }