1 /*******************************************************************************
2  * Intermediate structures used for generating class strings
3  *
4  * These are only used internally, so are not being exported
5  *
6  * Authors: Matthew Soucy, msoucy@csh.rit.edu
7  * Date: Oct 5, 2013
8  * Version: 0.0.2
9  */
10 module dproto.intermediate;
11 
12 import dproto.attributes;
13 import dproto.serialize;
14 
15 import std.algorithm;
16 import std.conv;
17 import std.string;
18 import std.format;
19 
20 package:
21 
22 struct Options {
23 	string[string] raw;
24 	alias raw this;
25 	const void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt)
26 	{
27 		if(fmt.spec == 'p') {
28 			if(!raw.length) return;
29 			sink.formattedWrite(" [%-(%s = %s%|, %)]", raw);
30 		} else {
31 			sink.formattedWrite(`["dproto_generated": "true"%(, %s : %s%)]`, raw);
32 		}
33 	}
34 }
35 
36 struct MessageType {
37 	this(string name) {
38 		this.name = name;
39 	}
40 	string name;
41 	Options options;
42 	Field[] fields;
43 	EnumType[] enumTypes;
44 	MessageType[] messageTypes;
45 
46 	const void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt)
47 	{
48 		if(fmt.spec == 'p') {
49 			sink.formattedWrite("message %s { ", name);
50 			foreach(opt, val; options) {
51 				sink.formattedWrite("option %s = %s; ", opt, val);
52 			}
53 		} else {
54 			sink.formattedWrite("static struct %s {\n", name);
55 
56 			// Methods for serialization and deserialization.
57 			sink("static import dproto.attributes;\n");
58 			sink(`mixin dproto.attributes.ProtoAccessors;`);
59 		}
60 		foreach(et; enumTypes) et.toString(sink, fmt);
61 		foreach(mt; messageTypes) mt.toString(sink, fmt);
62 		foreach(field; fields) field.toString(sink, fmt);
63 		sink("}\n");
64 	}
65 
66 	string toD() @property const { return "%s".format(this); }
67 }
68 
69 struct EnumType {
70 	this(string name) {
71 		this.name = name.idup;
72 	}
73 	string name;
74 	Options options;
75 	int[string] values;
76 
77 	const void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt)
78 	{
79 		sink.formattedWrite("enum %s {\n", name);
80 		string suffix = ", ";
81 		if(fmt.spec == 'p') {
82 			foreach(opt, val; options) {
83 				sink.formattedWrite("option %s = %s; ", opt, val);
84 			}
85 			suffix = "; ";
86 		}
87 		foreach(key, val; values) {
88 			sink.formattedWrite("%s = %s%s", key, val, suffix);
89 		}
90 		sink("}\n");
91 	}
92 
93 	string toD() @property const { return "%s".format(this); }
94 }
95 
96 struct Option {
97 	this(string name, string value) {
98 		this.name = name.idup;
99 		this.value = value.idup;
100 	}
101 	string name;
102 	string value;
103 }
104 
105 struct Extension {
106 	ulong minVal = 0;
107 	ulong maxVal = ulong.max;
108 }
109 
110 struct Dependency {
111 	this(string depname, bool isPublic = false) {
112 		this.name = depname;
113 		this.isPublic = isPublic;
114 	}
115 	string name;
116 	bool isPublic;
117 
118 	const void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt)
119 	{
120 		if(fmt.spec == 'p') {
121 			sink("import ");
122 			if(isPublic) {
123 				sink("public ");
124 			}
125 		   sink.formattedWrite(`"%s"; `, name);
126 		} else {
127 			sink.formattedWrite(`mixin ProtocolBuffer!"%s";`, name);
128 		}
129 	}
130 
131 	string toD() @property const { return "%s".format(this); }
132 }
133 
134 struct ProtoPackage {
135 	this(string fileName) {
136 		this.fileName = fileName.idup;
137 	}
138 	string fileName;
139 	string packageName;
140 	Dependency[] dependencies;
141 	EnumType[] enumTypes;
142 	MessageType[] messageTypes;
143 	Options options;
144 	Service[] rpcServices;
145 	string syntax = "proto2";
146 
147 	const void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt)
148 	{
149 		if(fmt.spec == 'p') {
150 			if(packageName) {
151 				sink.formattedWrite("package %s; ", packageName);
152 			}
153 			if(syntax != "proto2") {
154 				sink.formattedWrite(`syntax = %s; `, syntax);
155 			}
156 		}
157 		foreach(dep; dependencies) {
158 			dep.toString(sink, fmt);
159 		}
160 		foreach(e; enumTypes) e.toString(sink, fmt);
161 		foreach(m; messageTypes) m.toString(sink, fmt);
162 		foreach(r; rpcServices) r.toString(sink, fmt);
163 		if(fmt.spec == 'p') {
164 			foreach(opt, val; options) {
165 				sink.formattedWrite("option %s = %s; ", opt, val);
166 			}
167 		}
168 	}
169 
170 	string toProto() @property const { return "%p".format(this); }
171 	string toD() @property const { return "%s".format(this); }
172 }
173 
174 struct Field {
175 	enum Requirement {
176 		OPTIONAL,
177 		REPEATED,
178 		REQUIRED
179 	}
180 	this(Requirement labelEnum, string type, string name, uint tag, Options options) {
181 		this.requirement = labelEnum;
182 		this.type = type;
183 		this.name = name;
184 		this.id = tag;
185 		this.options = options;
186 	}
187 	Requirement requirement;
188 	string type;
189 	string name;
190 	uint id;
191 	Options options;
192 
193 	const bool hasDefaultValue() {
194 		return null != ("default" in options);
195 	}
196 	const string defaultValue() {
197 		return options["default"];
198 	}
199 
200 	const void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt)
201 	{
202 		switch(fmt.spec) {
203 			case 'p':
204 				if(fmt.width == 3 && requirement != Requirement.REPEATED) {
205 					sink.formattedWrite("%s %s = %s%p; ",
206 						type, name, id, options);
207 				}
208 				else {
209 					sink.formattedWrite("%s %s %s = %s%p; ",
210 						requirement.to!string.toLower(),
211 						type, name, id, options);
212 				}
213 				break;
214 			default:
215 				if(!fmt.flDash) {
216 					getDeclaration(sink);
217 				} else {
218 					sink.formattedWrite("%s %s;\n",
219 						type, name);
220 				}
221 				break;
222 		}
223 	}
224 
225 	void getDeclaration(scope void delegate(const(char)[]) sink) const {
226 		if(requirement == Requirement.REQUIRED) {
227 			sink("@(dproto.attributes.Required())\n");
228 		} else if(requirement == Requirement.REPEATED) {
229 			if(options.get("packed", "false") != "false") {
230 				sink("@(dproto.attributes.Packed())\n");
231 			}
232 		}
233 		sink("@(dproto.attributes.ProtoField");
234 		sink.formattedWrite(`("%s", %s)`, type, id);
235 		sink(")\n");
236 
237 		bool wrap_with_nullable =
238 			requirement == Requirement.OPTIONAL &&
239 			! type.isBuiltinType();
240 
241 		if(wrap_with_nullable) {
242 			sink(`dproto.serialize.PossiblyNullable!(`);
243 		}
244 		string typestr = type;
245 		if(type.isBuiltinType) {
246 			typestr = format(`BuffType!"%s"`, type);
247 		}
248 
249 		sink(typestr);
250 
251 		if(wrap_with_nullable) {
252 			sink(`)`);
253 		}
254 		if(requirement == Requirement.REPEATED) {
255 			sink("[]");
256 		}
257 		sink.formattedWrite(" %s", name);
258 		if (requirement != Requirement.REPEATED) {
259 			if (hasDefaultValue) {
260 				sink.formattedWrite(`= SpecifiedDefaultValue!(%s, "%s")`, typestr, defaultValue);
261 			} else if(type.isBuiltinType || ! wrap_with_nullable) {
262 				sink.formattedWrite("= UnspecifiedDefaultValue!(%s)", typestr);
263 			}
264 		}
265 		sink(";\n\n");
266 	}
267 	void getCase(scope void delegate(const(char)[]) sink) const {
268 		sink.formattedWrite("case %s:\n", id);
269 		sink.formattedWrite("%s.deserialize(__msgdata, __data);\n", name);
270 		if(requirement == Requirement.REQUIRED) {
271 			sink.formattedWrite("%s_isset = true;\n", name);
272 		}
273 		sink("break;\n");
274 	}
275 }
276 
277 
278 struct Service {
279 	this(string name) {
280 		this.name = name.idup;
281 	}
282 	string name;
283 	Options options;
284 	Method[] rpc;
285 
286 	struct Method {
287 		this(string name, string documentation, string requestType, string responseType) {
288 			this.name = name.idup;
289 			this.documentation = documentation.idup;
290 			this.requestType = requestType.idup;
291 			this.responseType = responseType.idup;
292 		}
293 		string name;
294 		string documentation;
295 		string requestType;
296 		string responseType;
297 		Options options;
298 
299 		const void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt)
300 		{
301 			switch(fmt.spec) {
302 				case 'p':
303 					sink.formattedWrite("rpc %s (%s) returns (%s)", name, requestType, responseType);
304 					if(options.length > 0) {
305 						sink(" {\n");
306 						foreach(opt, val; options) {
307 							sink.formattedWrite("option %s = %s;\n", opt, val);
308 						}
309 						sink("}\n");
310 					} else {
311 						sink(";\n");
312 					}
313 					break;
314 				default:
315 					if(fmt.precision == 3) {
316 						sink.formattedWrite("%s %s (%s) { %s res; return res; }\n", responseType, name, requestType, responseType);
317 					} else if(fmt.precision == 2) {
318 						sink.formattedWrite("void %s (const %s, ref %s);\n", name, requestType, responseType);
319 					} else if(fmt.precision == 1) {
320 						sink.formattedWrite("%s %s (%s);\n", responseType, name, requestType);
321 					}
322 					break;
323 			}
324 		}
325 	}
326 
327 	const void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt)
328 	{
329 		switch(fmt.spec) {
330 			case 'p':
331 				sink.formattedWrite("service %s {\n", name);
332 				break;
333 			default:
334 				if(fmt.precision == 3) {
335 					sink.formattedWrite("class %s {\n", name);
336 				} else if(fmt.precision == 2 || fmt.precision == 1) {
337 					sink.formattedWrite("interface %s {\n", name);
338 				} else {
339 					return;
340 				}
341 				break;
342 		}
343 		foreach(m; rpc) m.toString(sink, fmt);
344 		sink("}\n");
345 	}
346 }