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 }