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 }