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 }