1 // this file implements the structures and lexer for the protocol buffer format 2 // required to parse a protocol buffer file or tree and generate 3 // code to read and write the specified format 4 module dprotobuf.pbmessage; 5 6 import dprotobuf.pbgeneral; 7 import dprotobuf.pbenum; 8 import dprotobuf.pbchild; 9 import dprotobuf.pbextension; 10 11 version(D_Version2) { 12 import std.algorithm; 13 import std.range; 14 } else 15 import dprotobuf.d1support; 16 17 import std.conv; 18 import std.stdio; 19 import std..string; 20 21 // I intentionally left out all identifier validation routines, because the compiler knows how to resolve symbols. 22 // This means I don't have to write that code. 23 24 struct PBMessage { 25 string name; 26 string[] comments; 27 // message definitions that actually occur within this message 28 PBMessage[]message_defs; 29 // enum definitions that actually occur within this message 30 PBEnum[]enum_defs; 31 // variable/structure/enum instances 32 PBChild[]children; 33 // this is for the compiler to stuff things into when it finds applicable extensions to the class 34 PBChild[]child_exten; 35 // this is for actual read extensions 36 PBExtension[]extensions; 37 // these set the allowable bounds for extensions to this message 38 struct allow_exten { 39 int min=-1; 40 int max=-1; 41 } 42 allow_exten[]exten_sets; 43 44 // string-modifying constructor 45 static PBMessage opCall(ref ParserData pbstring) 46 in { 47 assert(pbstring.length); 48 } body { 49 // things we currently support in a message: messages, enums, and children(repeated, required, optional) 50 // first things first, rip off "message" 51 pbstring.input.skipOver("message"); 52 // now rip off the next set of whitespace 53 pbstring = stripLWhite(pbstring); 54 // get message name 55 string name = stripValidChars(CClass.Identifier,pbstring); 56 PBMessage message; 57 message.name = name; 58 // rip off whitespace 59 pbstring = stripLWhite(pbstring); 60 // make sure the next character is the opening { 61 if(!pbstring.input.skipOver("{")) { 62 throw new PBParseException("Message Definition","Expected next character to be '{'. You may have a space in your message name: "~name, pbstring.line); 63 } 64 65 // prep for loop spinup by removing extraneous whitespace 66 pbstring = stripLWhite(pbstring); 67 CommentManager storeComment; 68 69 // now we're ready to enter the loop and parse children 70 while(pbstring[0] != '}') { 71 // start parsing, we shouldn't have any whitespace here 72 auto curElementType = typeNextElement(pbstring); 73 auto curElementLine = pbstring.line; 74 switch(curElementType){ 75 case PBTypes.PB_Message: 76 message.message_defs ~= PBMessage(pbstring); 77 break; 78 case PBTypes.PB_Enum: 79 message.enum_defs ~= PBEnum(pbstring); 80 break; 81 case PBTypes.PB_Extend: 82 message.extensions ~= PBExtension(pbstring); 83 break; 84 case PBTypes.PB_Extension: 85 message.ripExtenRange(pbstring); 86 break; 87 case PBTypes.PB_Repeated: 88 case PBTypes.PB_Required: 89 case PBTypes.PB_Optional: 90 message.children ~= PBChild(pbstring); 91 break; 92 case PBTypes.PB_Comment: 93 // Preserve at least one spacing in comments 94 if(storeComment.line+1 < pbstring.line) 95 if(!storeComment.comments.empty()) 96 storeComment ~= ""; 97 storeComment ~= stripValidChars(CClass.Comment,pbstring); 98 storeComment.line = curElementLine; 99 if(curElementLine == storeComment.lastElementLine) 100 tryAttachComments(message, storeComment); 101 break; 102 case PBTypes.PB_MultiComment: 103 foreach(c; ripComment(pbstring)) 104 storeComment ~= c; 105 storeComment.line = pbstring.line; 106 break; 107 case PBTypes.PB_Option: 108 // rip of "option" and leading whitespace 109 pbstring.input.skipOver("option"); 110 pbstring = stripLWhite(pbstring); 111 ripOption(pbstring); 112 break; 113 default: 114 throw new PBParseException("Message Definition","Only extend, service, package, and message are allowed here.", pbstring.line); 115 } 116 pbstring.input.skipOver(";"); 117 // this needs to stay at the end 118 pbstring = stripLWhite(pbstring); 119 storeComment.lastElementType = curElementType; 120 storeComment.lastElementLine = curElementLine; 121 tryAttachComments(message, storeComment); 122 } 123 // rip off the } 124 pbstring.input.skipOver("}"); 125 return message; 126 } 127 128 static void tryAttachComments(ref PBMessage message, ref CommentManager storeComment) { 129 // Attach Comments to elements 130 if(!storeComment.comments.empty()) { 131 if(storeComment.line == storeComment.lastElementLine 132 || storeComment.line+3 > storeComment.lastElementLine) { 133 switch(storeComment.lastElementType) { 134 case PBTypes.PB_Comment: 135 case PBTypes.PB_MultiComment: 136 break; 137 case PBTypes.PB_Message: 138 message.message_defs[$-1].comments 139 = storeComment.comments; 140 goto default; 141 case PBTypes.PB_Enum: 142 message.enum_defs[$-1].comments 143 = storeComment.comments; 144 goto default; 145 case PBTypes.PB_Repeated: 146 case PBTypes.PB_Required: 147 case PBTypes.PB_Optional: 148 message.children[$-1].comments 149 = storeComment.comments; 150 goto default; 151 default: 152 storeComment.comments = null; 153 } 154 } 155 } 156 } 157 void ripExtenRange(ref ParserData pbstring) { 158 pbstring = pbstring["extensions".length..pbstring.length]; 159 pbstring = stripLWhite(pbstring); 160 allow_exten ext; 161 // expect next to be numeric 162 string tmp = stripValidChars(CClass.Numeric,pbstring); 163 if (!tmp.length) throw new PBParseException("Message Parse("~name~" extension range)","Unable to rip min and max for extension range", pbstring.line); 164 ext.min = to!(int)(tmp); 165 pbstring = stripLWhite(pbstring); 166 // make sure we have "to" 167 if (pbstring.input[0..2].icmp("to") != 0) { 168 throw new PBParseException("Message Parse("~name~" extension range)","Unable to rip min and max for extension range", pbstring.line); 169 } 170 // rip of "to" 171 pbstring = pbstring[2..pbstring.length]; 172 pbstring = stripLWhite(pbstring); 173 // check for "max" and rip it if necessary 174 if (pbstring.input[0..3].icmp("max") == 0) { 175 pbstring = pbstring[3..pbstring.length]; 176 // (1<<29)-1 is defined as the maximum extension value 177 ext.max = (1<<29)-1; 178 } else { 179 tmp = stripValidChars(CClass.Numeric,pbstring); 180 if (!tmp.length) throw new PBParseException("Message Parse("~name~" extension range)","Unable to rip min and max for extension range", pbstring.line); 181 ext.max = to!(int)(tmp); 182 if (ext.max > (1<<29)-1) { 183 throw new PBParseException("Message Parse("~name~" extension range)","Max defined extension value is greater than allowable max", pbstring.line); 184 } 185 } 186 pbstring = stripLWhite(pbstring); 187 // check for ; and rip it off 188 if (pbstring[0] != ';') { 189 throw new PBParseException("Message Parse("~name~" extension range)","Missing ; at end of extension range definition", pbstring.line); 190 } 191 pbstring = pbstring[1..pbstring.length]; 192 exten_sets ~= ext; 193 } 194 } 195 196 string genExtString(PBExtension[]extens,string indent) { 197 // we just need to generate a list of static const variables 198 string ret; 199 foreach(exten;extens) foreach(child;exten.children) { 200 ret ~= indent~"const int "~child.name~" = "~to!(string)(child.index)~";\n"; 201 } 202 return ret; 203 } 204 205 unittest { 206 auto instring = ParserData("message glorm{\noptional int32 i32test = 1;\nmessage simple { }\noptional simple quack = 5;\n}\n"); 207 208 writefln("unittest ProtocolBuffer.pbmessage"); 209 auto msg = PBMessage(instring); 210 assert(msg.name == "glorm"); 211 assert(msg.message_defs[0].name == "simple"); 212 assert(msg.children.length == 2); 213 214 auto str = ParserData("message Person { 215 // I comment types 216 message PhoneNumber { 217 /* Multi\n line\n*/ 218 required string number = 1; 219 optional PhoneType type = 2 ;// Their type of phone 220 }}"); 221 222 auto ms = PBMessage(str); 223 assert(ms.name == "Person"); 224 assert(ms.message_defs[0].name == "PhoneNumber"); 225 assert(ms.message_defs[0].comments[0] == "// I comment types"); 226 assert(ms.message_defs[0].children[0].comments[0] == "/* Multi"); 227 assert(ms.message_defs[0].children[0].comments[1] == " line"); 228 assert(ms.message_defs[0].children[0].comments[2] == "*/"); 229 assert(ms.message_defs[0].children[1].comments[0] == "// Their type of phone"); 230 }