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.serialize; 13 14 import std.algorithm; 15 import std.conv; 16 import std.string; 17 18 package: 19 20 alias Options = string[string]; 21 22 struct MessageType { 23 this(string name) { 24 this.name = name; 25 } 26 string name; 27 Options options; 28 Field[] fields; 29 EnumType[] enumTypes; 30 MessageType[] messageTypes; 31 32 string toProto() @property { 33 string ret; 34 ret ~= "message %s {".format(name); 35 foreach(opt, val;options) { 36 ret ~= "option %s = %s;".format(opt, val); 37 } 38 if(fields) { 39 ret ~= fields.map!(a=>a.toProto())().join(); 40 } 41 ret ~= enumTypes.map!(a=>a.toProto())().join("\n"); 42 ret ~= messageTypes.map!(a=>a.toProto())().join("\n"); 43 ret ~= "}"; 44 return ret; 45 } 46 47 string toD() { 48 return `struct %s { 49 %s 50 %s 51 %s 52 53 ubyte[] serialize() { 54 return %s; 55 } 56 57 void deserialize(ubyte[] data) { 58 // Required flags 59 %s 60 61 while(data.length) { 62 auto msgdata = data.readVarint(); 63 switch(msgdata.msgNum()) { 64 %s 65 default: { 66 /// @todo: Safely ignore unrecognized messages 67 defaultDecode(msgdata, data); 68 break; 69 } 70 } 71 } 72 73 // Check required flags 74 %s 75 } 76 77 this(ubyte[] data) { 78 deserialize(data); 79 } 80 }`.format( 81 name, 82 enumTypes.map!(a=>a.toD())().join(), 83 messageTypes.map!(a=>a.toD())().join(), 84 fields.map!(a=>a.getDeclaration())().join("\n\t"), 85 fields.map!(a=>a.name~".serialize()")().join(" ~ "), 86 fields.filter!(a=>a.requirement==Field.Requirement.REQUIRED)().map!(a=>"bool "~a.name~"_isset = false;")().join("\n\t\t"), 87 fields.map!(a=>a.getCase())().join("\n\t\t\t\t"), 88 fields.filter!(a=>a.requirement==Field.Requirement.REQUIRED)().map!(a=>a.getCheck())().join("\n\t\t") 89 ); 90 } 91 } 92 93 struct EnumType { 94 this(string name) { 95 this.name = name.idup; 96 } 97 string name; 98 Options options; 99 int[string] values; 100 101 string toProto() @property { 102 string ret; 103 ret ~= "enum %s {".format(name); 104 ret ~= "%(%s%)".format(options); 105 foreach(key, val;values) { 106 ret ~= "%s = %s;".format(key, val); 107 } 108 ret ~= "}"; 109 return ret; 110 } 111 string toD() @property { 112 string members; 113 foreach(key, val; values) { 114 members ~= "%s = %s, ".format(key, val); 115 } 116 string ret = `enum %s {%s} 117 %s readProto(string T)(ref ubyte[] src) if(T == "%s") { return src.readVarint().to!(%s)(); } 118 ubyte[] serialize(%s src) { return src.toVarint().dup; }`.format(name, members, name, name, name, name); 119 return ret; 120 } 121 } 122 123 struct Option { 124 this(string name, string value) { 125 this.name = name.idup; 126 this.value = value.idup; 127 } 128 string name; 129 string value; 130 } 131 132 struct Extension { 133 ulong minVal = 0; 134 ulong maxVal = ulong.max; 135 } 136 137 struct ProtoPackage { 138 this(string fileName) { 139 this.fileName = fileName.idup; 140 } 141 string fileName; 142 string packageName; 143 string[] dependencies; 144 EnumType[] enumTypes; 145 MessageType[] messageTypes; 146 Options options; 147 string toProto() @property { 148 string ret; 149 if(packageName) { 150 ret ~= "package %s;".format(packageName); 151 } 152 foreach(dep;dependencies) { 153 ret ~= `import "%s";`.format(dep); 154 } 155 foreach(e;enumTypes) { 156 ret ~= e.toProto(); 157 } 158 foreach(msg;messageTypes) { 159 ret ~= msg.toProto(); 160 } 161 if(options) { 162 ret ~= "%(%s%)".format(options); 163 } 164 return ret; 165 } 166 string toD() @property { 167 string ret; 168 foreach(dep;dependencies) { 169 ret ~= "mixin ProtocolBuffer!\"%s\";\n".format(dep); 170 } 171 foreach(e;enumTypes) { 172 ret ~= e.toD()~'\n'; 173 } 174 foreach(msg;messageTypes) { 175 ret ~= msg.toD()~'\n'; 176 } 177 return ret; 178 } 179 } 180 181 struct Field { 182 enum Requirement { 183 OPTIONAL, 184 REPEATED, 185 REQUIRED 186 } 187 this(Requirement labelEnum, string type, string name, uint tag, Options options) { 188 this.requirement = labelEnum; 189 this.type = type; 190 this.name = name; 191 this.id = tag; 192 this.options = options; 193 } 194 Requirement requirement; 195 string type; 196 string name; 197 uint id; 198 Options options; 199 string toProto() @property { 200 return "%s %s %s = %s%s;".format(requirement.to!string().toLower(), type, name, id, options.length?" ["~options.to!string()~']':""); 201 } 202 string getDeclaration() { 203 string ret; 204 with(Requirement) final switch(requirement) { 205 case OPTIONAL: ret ~= "Optional"; break; 206 case REPEATED: ret ~= "Repeated"; break; 207 case REQUIRED: ret ~= "Required"; break; 208 } 209 ret ~= `Buffer!(%s, "%s", `.format(id,type); 210 if(IsBuiltinType(type)) { 211 ret ~= `BuffType!"`~type~`"`; 212 } else { 213 ret ~= type; 214 } 215 if(auto dep = "deprecated" in options) { 216 ret ~= ", "~(*dep); 217 } else { 218 ret ~= ", false"; 219 } 220 if(requirement == Requirement.OPTIONAL) { 221 if(auto dV = "default" in options) { 222 string dVprefix; 223 if(!IsBuiltinType(type)) { 224 dVprefix = type~"."; 225 } 226 ret ~= ", "~(dVprefix)~(*dV); 227 } else if(IsBuiltinType(type)) { 228 ret ~= `, (BuffType!"%s").init`.format(type); 229 } else { 230 ret ~= `, %s.init`.format(type); 231 } 232 } else if(requirement == Requirement.REPEATED) { 233 auto packed = "packed" in options; 234 if(packed !is null && *packed == "true") { 235 ret ~= ", true"; 236 } else { 237 ret ~= ", false"; 238 } 239 } 240 ret ~= `) %s;`.format(name); 241 return ret; 242 } 243 string getCase() { 244 if(requirement == Requirement.REQUIRED) { 245 return "case %s: {%s.deserialize(msgdata, data);%s_isset = true;break;}".format(id.to!string(), name, name); 246 } else { 247 return "case %s: {%s.deserialize(msgdata, data);break;}".format(id.to!string(), name); 248 } 249 } 250 251 string getCheck() { 252 return `enforce(%s_isset, new DProtoException("Did not receive expected input %s"));`.format(name, name); 253 } 254 }