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, dproto@msoucy.me
7  */
8 module dproto.intermediate;
9 
10 import dproto.attributes;
11 import dproto.serialize;
12 
13 import std.algorithm;
14 import std.conv;
15 import std..string;
16 import std.format;
17 
18 package:
19 
20 struct Options {
21 	string[string] raw;
22 	alias raw this;
23 	const void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt)
24 	{
25 		if(fmt.spec == 'p') {
26 			if(!raw.length) return;
27 			sink.formattedWrite(" [%-(%s = %s%|, %)]", raw);
28 		} else {
29 			sink.formattedWrite(`["dproto_generated": "true"%(, %s : %s%)]`, raw);
30 		}
31 	}
32 }
33 
34 struct MessageType {
35 	this(string name) {
36 		this.name = name;
37 	}
38 	string name;
39 	Options options;
40 	Field[] fields;
41 	EnumType[] enumTypes;
42 	MessageType[] messageTypes;
43 
44 	const void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt)
45 	{
46 		if(fmt.spec == 'p') {
47 			sink.formattedWrite("message %s { ", name);
48 			foreach(opt, val; options) {
49 				sink.formattedWrite("option %s = %s; ", opt, val);
50 			}
51 		} else {
52 			sink.formattedWrite("static struct %s {\n", name);
53 
54 			// Methods for serialization and deserialization.
55 			sink("static import dproto.attributes;\n");
56 			sink("mixin dproto.attributes.ProtoAccessors;\n");
57 		}
58 		foreach(et; enumTypes) et.toString(sink, fmt);
59 		foreach(mt; messageTypes) mt.toString(sink, fmt);
60 		foreach(field; fields) field.toString(sink, fmt);
61 		sink("}\n");
62 	}
63 
64 	string toD() @property const { return "%s".format(this); }
65 }
66 
67 struct EnumType {
68 	this(string name) {
69 		this.name = name.idup;
70 	}
71 	string name;
72 	Options options;
73 	int[string] values;
74 
75 	const void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt)
76 	{
77 		sink.formattedWrite("enum %s {\n", name);
78 		string suffix = ", ";
79 		if(fmt.spec == 'p') {
80 			foreach(opt, val; options) {
81 				sink.formattedWrite("option %s = %s; ", opt, val);
82 			}
83 			suffix = "; ";
84 		}
85 		foreach(key, val; values) {
86 			sink.formattedWrite("%s = %s%s", key, val, suffix);
87 		}
88 		sink("}\n");
89 	}
90 
91 	string toD() @property const { return "%s".format(this); }
92 }
93 
94 struct Option {
95 	this(string name, string value) {
96 		this.name = name.idup;
97 		this.value = value.idup;
98 	}
99 	string name;
100 	string value;
101 }
102 
103 struct Extension {
104 	ulong minVal = 0;
105 	ulong maxVal = ulong.max;
106 }
107 
108 struct Dependency {
109 	this(string depname, bool isPublic = false, bool isWeak = false) {
110 		this.name = depname;
111 		this.isPublic = isPublic;
112 		this.isWeak = isWeak;
113 	}
114 	string name;
115 	bool isPublic;
116 	bool isWeak;
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;
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 !is null && 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 }