1 /*
2  * This file is part of gtkD.
3  *
4  * gtkD is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License
6  * as published by the Free Software Foundation; either version 3
7  * of the License, or (at your option) any later version, with
8  * some exceptions, please read the COPYING file.
9  *
10  * gtkD is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with gtkD; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
18  */
19 
20 module utils.GtkWrapper;
21 
22 import std.algorithm;
23 import std.array;
24 import std.file;
25 import std.uni;
26 import std.path;
27 import std.stdio;
28 
29 import utils.DefReader;
30 import utils.IndentedStringBuilder;
31 import utils.GtkPackage;
32 import utils.GtkStruct;
33 import utils.GtkFunction;
34 import utils.GtkType;
35 import utils.WrapError;
36 
37 void main(string[] args)
38 {
39 	GtkWrapper wrapper = new GtkWrapper("./");
40 	wrapper.proccess("APILookup.txt");
41 
42 	if ( args.length > 1 && args[1].among("-f", "--print-free") )
43 		wrapper.printFreeFunctions();
44 
45 	foreach(pack; GtkWrapper.packages)
46 	{
47 		if ( pack.name == "cairo" )
48 			continue;
49 
50 		pack.writeTypes();
51 		pack.writeLoaderTable();
52 		pack.writeClasses();
53 	}
54 }
55 
56 class GtkWrapper
57 {
58 	bool includeComments;
59 
60 	string apiRoot;
61 	string inputRoot;
62 	string outputRoot;
63 	string srcDir;
64 	string bindDir;
65 
66 	static string licence;
67 	static string[string] aliasses;
68 
69 	static GtkPackage[string] packages;
70 
71 	public this(string apiRoot)
72 	{
73 		this.apiRoot = apiRoot;
74 	}
75 
76 	public void proccess(string apiLookupDefinition)
77 	{
78 		DefReader defReader = new DefReader( buildPath(apiRoot, apiLookupDefinition) );
79 
80 		while ( !defReader.empty )
81 		{
82 			switch ( defReader.key )
83 			{
84 				case "license":
85 					licence = defReader.readBlock().join();
86 					break;
87 				case "includeComments":
88 					includeComments = defReader.valueBool;
89 					break;
90 				case "alias":
91 					loadAA(aliasses, defReader);
92 					break;
93 				case "inputRoot":
94 					inputRoot = defReader.value;
95 					break;
96 				case "outputRoot":
97 					outputRoot = defReader.value;
98 					break;
99 				case "srcDir":
100 					srcDir = defReader.value;
101 					break;
102 				case "bindDir":
103 					bindDir = defReader.value;
104 					break;
105 				case "copy":
106 					if ( srcDir.empty )
107 						throw new WrapError(defReader, "Can't copy the file when srcDir is not set");
108 
109 					string outDir = buildPath(outputRoot, srcDir);
110 
111 					if ( !exists(outDir) )
112 					{
113 						try
114 							mkdirRecurse(outDir);
115 						catch (FileException)
116 							throw new WrapError(defReader, "Failed to create directory: "~ outDir);
117 					}
118 
119 					copyFile(apiRoot, buildPath(outputRoot, srcDir), defReader.value);
120 					break;
121 				case "lookup":
122 					proccess(defReader.value);
123 					break;
124 				case "wrap":
125 					if ( inputRoot.empty )
126 						throw new WrapError(defReader, "Found wrap while inputRoot isn't set");
127 					if ( outputRoot.empty )
128 						throw new WrapError(defReader, "Found wrap while outputRoot isn't set");
129 					if ( srcDir.empty )
130 						throw new WrapError(defReader, "Found wrap while srcDir isn't set");
131 					if ( bindDir.empty )
132 						throw new WrapError(defReader, "Found wrap while bindDir isn't set");
133 
134 					wrapPackage(defReader);
135 					break;
136 				default:
137 					throw new WrapError(defReader, "Unknown key: "~defReader.key);
138 			}
139 
140 			defReader.popFront();
141 		}
142 	}
143 
144 	public void wrapPackage(DefReader defReader)
145 	{
146 		GtkPackage pack;
147 		GtkStruct currentStruct;
148 
149 		try
150 		{
151 			if (defReader.value in packages)
152 				throw new WrapError(defReader, "Package: "~ defReader.value ~"already defined.");
153 
154 			pack = new GtkPackage(defReader.value, this, srcDir, bindDir);
155 			packages[defReader.value] = pack;
156 			defReader.popFront();
157 		}
158 		catch (Exception e)
159 			throw new WrapError(defReader, e.msg);
160 
161 		while ( !defReader.empty )
162 		{
163 			switch ( defReader.key )
164 			{
165 				case "addAliases":
166 					pack.lookupAliases ~= defReader.readBlock();
167 					break;
168 				case "addEnums":
169 					pack.lookupEnums ~= defReader.readBlock();
170 					break;
171 				case "addStructs":
172 					pack.lookupStructs ~= defReader.readBlock();
173 					break;
174 				case "addFuncts":
175 					pack.lookupFuncts ~= defReader.readBlock();
176 					break;
177 				case "addConstants":
178 					pack.lookupConstants ~= defReader.readBlock();
179 					break;
180 				case "file":
181 					pack.parseGIR(defReader.value);
182 					break;
183 				case "struct":
184 					if ( defReader.value.empty )
185 					{
186 						currentStruct = null;
187 					}
188 					else
189 					{
190 						currentStruct = pack.getStruct(defReader.value);
191 						if ( currentStruct is null )
192 							currentStruct = createClass(pack, defReader.value);
193 					}
194 					break;
195 				case "class":
196 					if ( currentStruct is null )
197 						currentStruct = createClass(pack, defReader.value);
198 
199 					currentStruct.lookupClass = true;
200 					currentStruct.name = defReader.value;
201 					break;
202 				case "interface":
203 					if ( currentStruct is null )
204 						currentStruct = createClass(pack, defReader.value);
205 
206 					currentStruct.lookupInterface = true;
207 					currentStruct.name = defReader.value;
208 					break;
209 				case "cType":
210 					currentStruct.cType = defReader.value;
211 					break;
212 				case "namespace":
213 					currentStruct.type = GtkStructType.Record;
214 					currentStruct.lookupClass = false;
215 					currentStruct.lookupInterface = false;
216 
217 					if ( defReader.value.empty )
218 					{
219 						currentStruct.noNamespace = true;
220 					}
221 					else
222 					{
223 						currentStruct.noNamespace = false;
224 						currentStruct.name = defReader.value;
225 					}
226 					break;
227 				case "extend":
228 					currentStruct.lookupParent = true;
229 					currentStruct.parent = defReader.value;
230 					break;
231 				case "implements":
232 					if ( defReader.value.empty )
233 						currentStruct.implements = null;
234 					else
235 						currentStruct.implements ~= defReader.value;
236 					break;
237 				case "merge":
238 					GtkStruct mergeStruct = pack.getStruct(defReader.value);
239 					currentStruct.merge(mergeStruct);
240 					GtkStruct copy = currentStruct.dup();
241 					copy.noCode = true;
242 					copy.noExternal = true;
243 					mergeStruct.pack.collectedStructs[mergeStruct.name] = copy;
244 					break;
245 				case "move":
246 					string[] vals = defReader.value.split();
247 					if ( vals.length <= 1 )
248 						throw new WrapError(defReader, "No destination for move: "~ defReader.value);
249 					string newFuncName = ( vals.length == 3 ) ? vals[2] : vals[0];
250 					GtkStruct dest = pack.getStruct(vals[1]);
251 					if ( dest is null )
252 						dest = createClass(pack, vals[1]);
253 					if ( vals[0] !in pack.collectedFunctions )
254 						throw new WrapError(defReader, "unknown function "~ vals[0]);
255 					pack.collectedFunctions[vals[0]].strct = dest;
256 					dest.functions[newFuncName] = pack.collectedFunctions[vals[0]];
257 					dest.functions[newFuncName].name = newFuncName;
258 					pack.collectedFunctions.remove(vals[0]);
259 					break;
260 				case "import":
261 					currentStruct.imports ~= defReader.value;
262 					break;
263 				case "structWrap":
264 					loadAA(currentStruct.structWrap, defReader);
265 					break;
266 				case "alias":
267 					loadAA(currentStruct.aliases, defReader);
268 					break;
269 				case "override":
270 					currentStruct.functions[defReader.value].lookupOverride = true;
271 					break;
272 				case "noAlias":
273 					pack.collectedAliases.remove(defReader.value);
274 					break;
275 				case "noEnum":
276 					pack.collectedEnums.remove(defReader.value);
277 					break;
278 				case "noCallback":
279 					pack.collectedCallbacks.remove(defReader.value);
280 					break;
281 				case "noCode":
282 					if ( defReader.valueBool )
283 					{
284 						currentStruct.noCode = true;
285 						break;
286 					}
287 					if ( defReader.value !in currentStruct.functions )
288 						throw new WrapError(defReader, "Unknown function: "~ defReader.value);
289 
290 					currentStruct.functions[defReader.value].noCode = true;
291 					break;
292 				case "noExternal":
293 					currentStruct.noExternal = true;
294 					break;
295 				case "noSignal":
296 					currentStruct.functions[defReader.value~"-signal"].noCode = true;
297 					break;
298 				case "noStruct":
299 					currentStruct.noDecleration = true;
300 					break;
301 				case "code":
302 					currentStruct.lookupCode ~= defReader.readBlock;
303 					break;
304 				case "interfaceCode":
305 					currentStruct.lookupInterfaceCode ~= defReader.readBlock;
306 					break;
307 				case "in":
308 					string[] vals = defReader.value.split();
309 					if ( vals[0] !in currentStruct.functions )
310 						throw new WrapError(defReader, "Unknown function: "~ vals[0]);
311 					findParam(currentStruct, vals[0], vals[1]).direction = GtkParamDirection.Default;
312 					break;
313 				case "out":
314 					string[] vals = defReader.value.split();
315 					if ( vals[0] !in currentStruct.functions )
316 						throw new WrapError(defReader, "Unknown function: "~ vals[0]);
317 					findParam(currentStruct, vals[0], vals[1]).direction = GtkParamDirection.Out;
318 					break;
319 				case "inout":
320 				case "ref":
321 					string[] vals = defReader.value.split();
322 					if ( vals[0] !in currentStruct.functions )
323 						throw new WrapError(defReader, "Unknown function: "~ vals[0]);
324 					findParam(currentStruct, vals[0], vals[1]).direction = GtkParamDirection.InOut;
325 					break;
326 				case "array":
327 					string[] vals = defReader.value.split();
328 
329 					if ( vals[0] !in currentStruct.functions )
330 						throw new WrapError(defReader, "Unknown function: "~ vals[0]);
331 
332 					GtkFunction func = currentStruct.functions[vals[0]];
333 
334 					if ( vals[1] == "Return" )
335 					{
336 						if ( vals.length < 3 )
337 						{
338 							func.returnType.zeroTerminated = true;
339 							break;
340 						}
341 
342 						foreach( i, p; func.params )
343 						{
344 							if ( p.name == vals[2] )
345 								func.returnType.length = cast(int)i;
346 						}
347 					}
348 					else
349 					{
350 						GtkParam param = findParam(currentStruct, vals[0], vals[1]);
351 						GtkType elementType = new GtkType(this);
352 
353 						elementType.name = param.type.name;
354 						elementType.cType = param.type.cType[0..$-1];
355 						param.type.elementType = elementType;
356 
357 						if ( vals.length < 3 )
358 						{
359 							param.type.zeroTerminated = true;
360 							break;
361 						}
362 
363 						if ( vals[2] == "Return" )
364 						{
365 							param.type.length = -2;
366 							break;
367 						}
368 
369 						foreach( i, p; func.params )
370 						{
371 							if ( p.name == vals[2] )
372 								param.type.length = cast(int)i;
373 						}
374 					}
375 					break;
376 				case "copy":
377 					if ( srcDir.empty )
378 						throw new WrapError(defReader,
379 						                    "Can't copy the file when srcDir is not set");
380 
381 					copyFile(apiRoot, buildPath(outputRoot, srcDir), defReader.value);
382 					break;
383 				default:
384 					throw new WrapError(defReader, "Unknown key: "~defReader.key);
385 			}
386 
387 			defReader.popFront();
388 		}
389 	}
390 
391 	void printFreeFunctions()
392 	{
393 		foreach ( pack; packages )
394 		{
395 			foreach ( func; pack.collectedFunctions )
396 			{
397 				if ( func.movedTo.empty )
398 					writefln("%s: %s", pack.name, func.name);
399 			}
400 		}
401 	}
402 
403 	private GtkParam findParam(GtkStruct strct, string func, string name)
404 	{
405 		foreach( param; strct.functions[func].params )
406 		{
407 			if ( param.name == name )
408 				return param;
409 		}
410 
411 		return null;
412 	}
413 
414 	private void loadAA (ref string[string] aa, DefReader defReader)
415 	{
416 		string[] vals = defReader.value.split();
417 
418 		if ( vals.length == 1 )
419 			vals ~= "";
420 
421 		if ( vals.length == 2 )
422 			aa[vals[0]] = vals[1];
423 		else
424 			throw new WrapError(defReader, "Unknown key: "~defReader.key);
425 	}
426 
427 	private void copyFile(string srcDir, string destDir, string file)
428 	{
429 		string from = buildPath(srcDir, file);
430 		string to   = buildPath(destDir, file);
431 
432 		writefln("copying file [%s] to [%s]", from, to);
433 		copy(from, to);
434 
435 		if ( !exists(to) )
436 			throw new Exception("Cannot copy  file: "~from);
437 	}
438 
439 	private GtkStruct createClass(GtkPackage pack, string name)
440 	{
441 		GtkStruct strct = new GtkStruct(this, pack);
442 		strct.name = name;
443 		strct.cType = pack.cTypePrefix ~ name;
444 		strct.type = GtkStructType.Record;
445 		strct.noDecleration = true;
446 		pack.collectedStructs["lookup"~name] = strct;
447 
448 		return strct;
449 	}
450 }
451 
452 /**
453  * Apply aliasses to the tokens in the string, and
454  * camelCase underscore separated tokens.
455  */
456 string stringToGtkD(string str, string[string] aliases, string[string] localAliases = null)
457 {
458 	size_t pos, start;
459 	string seps = " \n\r\t\f\v()[]*,;";
460 	auto converted = appender!string();
461 
462 	while ( pos < str.length )
463 	{
464 		if ( !seps.canFind(str[pos]) )
465 		{
466 			start = pos;
467 
468 			while ( pos < str.length && !seps.canFind(str[pos]) )
469 				pos++;
470 
471 			//Workaround for the tm struct, type and variable have the same name.
472 			if ( pos < str.length && str[pos] == '*' && str[start..pos] == "tm" )
473 				converted.put("void");
474 			else
475 				converted.put(tokenToGtkD(str[start..pos], aliases, localAliases));
476 
477 			if ( pos == str.length )
478 				break;
479 		}
480 
481 		converted.put(str[pos]);
482 		pos++;
483 	}
484 
485 	return converted.data;
486 }
487 
488 unittest
489 {
490 	assert(stringToGtkD("token", ["token":"tok"]) == "tok");
491 	assert(stringToGtkD("string token_to_gtkD(string token, string[string] aliases)", ["token":"tok"])
492 	       == "string tokenToGtkD(string tok, string[string] aliases)");
493 }
494 
495 string tokenToGtkD(string token, string[string] aliases, bool caseConvert=true)
496 {
497 	return tokenToGtkD(token, aliases, null, caseConvert);
498 }
499 
500 string tokenToGtkD(string token, string[string] aliases, string[string] localAliases, bool caseConvert=true)
501 {
502 	if ( token in localAliases )
503 		return localAliases[token];
504 	else if ( token in aliases )
505 		return aliases[token];
506 	else if ( token.startsWith("cairo_") && token.endsWith("_t", "_t*", "_t**") )
507 		return token;
508 	else if ( token == "pid_t" )
509 		return token;
510 	else if ( caseConvert )
511 		return tokenToGtkD(removeUnderscore(token), aliases, localAliases, false);
512 	else
513 		return token;
514 }
515 
516 string removeUnderscore(string token)
517 {
518 	char pc;
519 	auto converted = appender!string();
520 
521 	while ( !token.empty )
522 	{
523 		if ( token[0] == '_' )
524 		{
525 			pc = token[0];
526 			token = token[1..$];
527 
528 			continue;
529 		}
530 
531 		if ( pc == '_' )
532 			converted.put(token[0].toUpper());
533 		else
534 			converted.put(token[0]);
535 
536 		pc = token[0];
537 		token = token[1..$];
538 	}
539 
540 	return converted.data;
541 }
542 
543 unittest
544 {
545 	assert(removeUnderscore("this_is_a_test") == "thisIsATest");
546 }