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 }