1 /*
2  * This file is part of gir-to-d.
3  *
4  * gir-to-d 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.
8  *
9  * gir-to-d is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with gir-to-d.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 module gtd.GirWrapper;
19 
20 import std.algorithm;
21 import std.array;
22 import std.file;
23 import std.uni;
24 import std.path;
25 import std.stdio;
26 import std.string;
27 
28 import gtd.DefReader;
29 import gtd.GirField;
30 import gtd.GirFunction;
31 import gtd.GirPackage;
32 import gtd.GirStruct;
33 import gtd.GirType;
34 import gtd.GirVersion;
35 import gtd.GlibTypes;
36 import gtd.IndentedStringBuilder;
37 import gtd.Log;
38 
39 enum PrintFileMethod
40 {
41 	Absolute,
42 	Relative,
43 	Default
44 }
45 
46 class GirWrapper
47 {
48 	bool includeComments = true;
49 	bool useRuntimeLinker;
50 	bool useBindDir;
51 
52 	bool printFiles;
53 	PrintFileMethod printFileMethod = PrintFileMethod.Default;
54 	string cwdOrBaseDirectory;
55 
56 	string inputDir;
57 	string outputDir;
58 	string srcDir = "./";
59 	string commandlineGirPath;
60 
61 	static string licence;
62 	static string[string] aliasses;
63 
64 	static GirPackage[string] packages;
65 
66 	public this(string inputDir, string outputDir)
67 	{
68 		this.inputDir         = inputDir;
69 		this.outputDir       = outputDir;
70 	}
71 
72 	void proccess(string lookupFileName)
73 	{
74 		if ( !exists(buildPath(inputDir, lookupFileName)) )
75 			error(lookupFileName, " not found, check '--help' for more information.");
76 
77 		DefReader defReader = new DefReader( buildPath(inputDir, lookupFileName) );
78 
79 		proccess(defReader);
80 	}
81 
82 	void proccess(DefReader defReader, GirPackage currentPackage = null, bool isDependency = false, GirStruct currentStruct = null)
83 	{
84 		while ( !defReader.empty )
85 		{
86 			if ( !currentPackage && defReader.key.among(
87 					"addAliases", "addConstants", "addEnums", "addFuncts", "addStructs", "file", "move",
88 					"struct", "class", "interface", "namespace", "noAlias", "noConstant", "noEnum", "noCallback") )
89 				error("Found: '", defReader.key, "' before wrap.", defReader);
90 
91 			if ( !currentStruct && defReader.key.among(
92 					"code", "cType", "extend", "implements", "import", "interfaceCode", "merge",
93 					"noCode", "noExternal", "noProperty", "noSignal", "noStruct", "override", "structWrap",
94 					"array", "in", "out", "inout", "ref") )
95 				error("Found: '", defReader.key, "' without an active struct.", defReader);
96 
97 			switch ( defReader.key )
98 			{
99 				//Toplevel keys.
100 				case "bindDir":
101 					warning("Don't use bindDir, it is no longer used since the c definitions have moved.", defReader);
102 					break;
103 				case "includeComments":
104 					includeComments = defReader.valueBool;
105 					break;
106 				case "inputRoot":
107 					warning("Don't use inputRoot, it has been removed as it was never implemented.", defReader);
108 					break;
109 				case "license":
110 					licence = defReader.readBlock().join();
111 					break;
112 				case "outputRoot":
113 					if ( outputDir == "./out" )
114 						outputDir = defReader.value;
115 					break;
116 
117 				//Global keys.
118 				case "alias":
119 					if ( currentStruct )
120 						loadAA(currentStruct.aliases, defReader);
121 					else
122 						loadAA(aliasses, defReader);
123 					break;
124 				case "copy":
125 					try
126 						copyFiles(inputDir, buildPath(outputDir, srcDir), defReader.value);
127 					catch(FileException ex)
128 						error(ex.msg, defReader);
129 					break;
130 				case "dependency":
131 					loadDependency(defReader);
132 					break;
133 				case "lookup":
134 					DefReader reader = new DefReader( buildPath(inputDir, defReader.value) );
135 
136 					proccess(reader, currentPackage, isDependency, currentStruct);
137 					break;
138 				case "srcDir":
139 					srcDir = defReader.value;
140 					break;
141 				case "version":
142 					if ( defReader.value == "end" )
143 						break;
144 
145 					if ( defReader.subKey.empty )
146 						error("No version specified.", defReader);
147 
148 					bool parseVersion = checkOsVersion(defReader.subKey);
149 					bool smalerThen = false;
150 
151 					if ( defReader.subKey.length > 1 && defReader.subKey[0] == '<' && defReader.subKey[1].isNumber() )
152 					{
153 						smalerThen = true;
154 						defReader.subKey.popFront();
155 					}
156 
157 					if ( !parseVersion && defReader.subKey[0].isNumber() )
158 					{
159 						if ( !currentPackage )
160 							error("Only use OS versions before wrap.", defReader);
161 						parseVersion = defReader.subKey <= currentPackage._version;
162 
163 						if ( smalerThen )
164 							parseVersion = !parseVersion;
165 					}
166 
167 					if ( defReader.value == "start" )
168 					{
169 						if ( parseVersion )
170 							break;
171 
172 						defReader.skipBlock();
173 					}
174 
175 					if ( !parseVersion )
176 						break;
177 
178 					size_t index = defReader.value.indexOf(':');
179 					defReader.key = defReader.value[0 .. max(index, 0)].strip();
180 					defReader.value = defReader.value[index +1 .. $].strip();
181 
182 					if ( !defReader.key.empty )
183 						continue;
184 
185 					break;
186 				case "wrap":
187 					if ( isDependency )
188 					{
189 						currentPackage.name = defReader.value;
190 						break;
191 					}
192 
193 					if ( outputDir.empty )
194 						error("Found wrap while outputRoot isn't set", defReader);
195 					if (defReader.value in packages)
196 						error("Package '", defReader.value, "' is already defined.", defReader);
197 
198 					currentStruct = null;
199 					currentPackage = new GirPackage(defReader.value, this, srcDir);
200 					packages[defReader.value] = currentPackage;
201 					break;
202 
203 				//Package keys
204 				case "addAliases":
205 					currentPackage.lookupAliases ~= defReader.readBlock();
206 					break;
207 				case "addConstants":
208 					currentPackage.lookupConstants ~= defReader.readBlock();
209 					break;
210 				case "addEnums":
211 					currentPackage.lookupEnums ~= defReader.readBlock();
212 					break;
213 				case "addFuncts":
214 					currentPackage.lookupFuncts ~= defReader.readBlock();
215 					break;
216 				case "addStructs":
217 					currentPackage.lookupStructs ~= defReader.readBlock();
218 					break;
219 				case "file":
220 					if ( !isAbsolute(defReader.value) )
221 					{
222 						currentPackage.parseGIR(getAbsoluteGirPath(defReader.value));
223 					}
224 					else
225 					{
226 						warning("Don't use absolute paths for specifying gir files.", defReader);
227 
228 						currentPackage.parseGIR(defReader.value);
229 					}
230 					break;
231 				case "move":
232 					string[] vals = defReader.value.split();
233 					if ( vals.length <= 1 )
234 						error("No destination for move: ", defReader.value, defReader);
235 					string newFuncName = ( vals.length == 3 ) ? vals[2] : vals[0];
236 					GirStruct dest = currentPackage.getStruct(vals[1]);
237 					if ( dest is null )
238 						dest = createClass(currentPackage, vals[1]);
239 
240 					if ( currentStruct && vals[0] in currentStruct.functions )
241 					{
242 						currentStruct.functions[vals[0]].strct = dest;
243 						dest.functions[newFuncName] = currentStruct.functions[vals[0]];
244 						dest.functions[newFuncName].name = newFuncName;
245 						if ( newFuncName.startsWith("new") )
246 							dest.functions[newFuncName].type = GirFunctionType.Constructor;
247 						if ( currentStruct.virtualFunctions.canFind(vals[0]) )
248 							dest.virtualFunctions ~= newFuncName;
249 						currentStruct.functions.remove(vals[0]);
250 					}
251 					else if ( vals[0] in currentPackage.collectedFunctions )
252 					{
253 						currentPackage.collectedFunctions[vals[0]].strct = dest;
254 						dest.functions[newFuncName] = currentPackage.collectedFunctions[vals[0]];
255 						dest.functions[newFuncName].name = newFuncName;
256 						currentPackage.collectedFunctions.remove(vals[0]);
257 					}
258 					else
259 						error("Unknown function ", vals[0], defReader);
260 					break;
261 				case "noAlias":
262 					currentPackage.collectedAliases.remove(defReader.value);
263 					break;
264 				case "noConstant":
265 					currentPackage.collectedConstants.remove(defReader.value);
266 					break;
267 				case "noEnum":
268 					currentPackage.collectedEnums.remove(defReader.value);
269 					break;
270 				case "noCallback":
271 					currentPackage.collectedCallbacks.remove(defReader.value);
272 					break;
273 				case "struct":
274 					if ( defReader.value.empty )
275 					{
276 						currentStruct = null;
277 					}
278 					else
279 					{
280 						currentStruct = currentPackage.getStruct(defReader.value);
281 						if ( currentStruct is null )
282 							currentStruct = createClass(currentPackage, defReader.value);
283 					}
284 					break;
285 
286 				//Struct keys.
287 				case "array":
288 					string[] vals = defReader.value.split();
289 
290 					if ( vals[0] in currentStruct.functions )
291 					{
292 						GirFunction func = currentStruct.functions[vals[0]];
293 
294 						if ( vals[1] == "Return" )
295 						{
296 							if ( vals.length < 3 )
297 							{
298 								func.returnType.zeroTerminated = true;
299 								break;
300 							}
301 
302 							GirType elementType = new GirType(this);
303 
304 							elementType.name = func.returnType.name;
305 							elementType.cType = func.returnType.cType[0..$-1];
306 							func.returnType.elementType = elementType;
307 							func.returnType.girArray = true;
308 
309 							foreach( i, p; func.params )
310 							{
311 								if ( p.name == vals[2] )
312 									func.returnType.length = cast(int)i;
313 							}
314 						}
315 						else
316 						{
317 							GirParam param = findParam(currentStruct, vals[0], vals[1]);
318 							GirType elementType = new GirType(this);
319 
320 							if ( !param )
321 								error("Unknown parameter ", vals[1], " in function ", vals[0]);
322 
323 							elementType.name = param.type.name;
324 							elementType.cType = param.type.cType[0..$-1];
325 							param.type.elementType = elementType;
326 							param.type.girArray = true;
327 
328 							if ( vals.length < 3 )
329 							{
330 								param.type.zeroTerminated = true;
331 								break;
332 							}
333 
334 							if ( vals[2] == "Return" )
335 							{
336 								param.type.length = -2;
337 								break;
338 							}
339 
340 							foreach( i, p; func.params )
341 							{
342 								if ( p.name == vals[2] )
343 									param.type.length = cast(int)i;
344 							}
345 						}
346 					}
347 					else if ( currentStruct.fields.map!(a => a.name).canFind(vals[0]) )
348 					{
349 						GirField arrayField;
350 						int lengthID = -1;
351 
352 						foreach ( size_t i, field; currentStruct.fields )
353 						{
354 							if ( field.name == vals[0] )
355 								arrayField = field;
356 							else if ( field.name == vals[1] )
357 								lengthID = cast(int)i;
358 
359 							if ( arrayField && lengthID > -1 )
360 								break;
361 						}
362 
363 						arrayField.type.length = lengthID;
364 						currentStruct.fields[lengthID].isLength = true;
365 
366 						GirType elementType = new GirType(this);
367 						elementType.name = arrayField.type.name;
368 						elementType.cType = arrayField.type.cType[0..$-1];
369 						arrayField.type.elementType = elementType;
370 						arrayField.type.girArray = true;
371 					}
372 					else
373 					{
374 						error("Field or function: `", vals[0], "' is unknown.", defReader);
375 					}
376 					break;
377 				case "class":
378 					if ( currentStruct is null )
379 						currentStruct = createClass(currentPackage, defReader.value);
380 
381 					currentStruct.lookupClass = true;
382 					currentStruct.name = defReader.value;
383 					break;
384 				case "code":
385 					currentStruct.lookupCode ~= defReader.readBlock;
386 					break;
387 				case "cType":
388 					currentStruct.cType = defReader.value;
389 					break;
390 				case "extend":
391 					currentStruct.lookupParent = true;
392 					currentStruct.parent = defReader.value;
393 					break;
394 				case "implements":
395 					if ( defReader.value.empty )
396 						currentStruct.implements = null;
397 					else
398 						currentStruct.implements ~= defReader.value;
399 					break;
400 				case "import":
401 					currentStruct.imports ~= defReader.value;
402 					break;
403 				case "interface":
404 					if ( currentStruct is null )
405 						currentStruct = createClass(currentPackage, defReader.value);
406 
407 					currentStruct.lookupInterface = true;
408 					currentStruct.name = defReader.value;
409 					break;
410 				case "interfaceCode":
411 					currentStruct.lookupInterfaceCode ~= defReader.readBlock;
412 					break;
413 				case "merge":
414 					GirStruct mergeStruct = currentPackage.getStruct(defReader.value);
415 					currentStruct.merge(mergeStruct);
416 					GirStruct copy = currentStruct.dup();
417 					copy.noCode = true;
418 					copy.noExternal = true;
419 					mergeStruct.pack.collectedStructs[defReader.value] = copy;
420 					break;
421 				case "namespace":
422 					currentStruct.type = GirStructType.Record;
423 					currentStruct.lookupClass = false;
424 					currentStruct.lookupInterface = false;
425 
426 					if ( defReader.value.empty )
427 					{
428 						currentStruct.noNamespace = true;
429 					}
430 					else
431 					{
432 						currentStruct.noNamespace = false;
433 						currentStruct.name = defReader.value;
434 					}
435 					break;
436 				case "noCode":
437 					if ( defReader.valueBool )
438 					{
439 						currentStruct.noCode = true;
440 						break;
441 					}
442 					if ( defReader.value !in currentStruct.functions )
443 						error("Unknown function ", defReader.value, ". Possible values: ", currentStruct.functions.keys, defReader);
444 
445 					currentStruct.functions[defReader.value].noCode = true;
446 					break;
447 				case "noExternal":
448 					currentStruct.noExternal = true;
449 					break;
450 				case "noProperty":
451 					foreach ( field; currentStruct.fields )
452 					{
453 						if ( field.name == defReader.value )
454 						{
455 							field.noProperty = true;
456 							break;
457 						}
458 						else if ( field == currentStruct.fields.back )
459 							error("Unknown field ", defReader.value, defReader);
460 					}
461 					break;
462 				case "noSignal":
463 					currentStruct.functions[defReader.value~"-signal"].noCode = true;
464 					break;
465 				case "noStruct":
466 					currentStruct.noDeclaration = true;
467 					break;
468 				case "structWrap":
469 					loadAA(currentStruct.structWrap, defReader);
470 					break;
471 
472 				//Function keys
473 				case "in":
474 					string[] vals = defReader.value.split();
475 					if ( vals[0] !in currentStruct.functions )
476 						error("Unknown function ", vals[0], ". Possible values: ", currentStruct.functions, defReader);
477 					GirParam param = findParam(currentStruct, vals[0], vals[1]);
478 					if ( !param )
479 						error("Unknown parameter ", vals[1], " in function ", vals[0]);
480 					param.direction = GirParamDirection.Default;
481 					break;
482 				case "out":
483 					string[] vals = defReader.value.split();
484 					if ( vals[0] !in currentStruct.functions )
485 						error("Unknown function ", vals[0], ". Possible values: ", currentStruct.functions, defReader);
486 					GirParam param = findParam(currentStruct, vals[0], vals[1]);
487 					if ( !param )
488 						error("Unknown parameter ", vals[1], " in function ", vals[0]);
489 					param.direction = GirParamDirection.Out;
490 					break;
491 				case "override":
492 					currentStruct.functions[defReader.value].lookupOverride = true;
493 					break;
494 				case "inout":
495 				case "ref":
496 					string[] vals = defReader.value.split();
497 					if ( vals[0] !in currentStruct.functions )
498 						error("Unknown function ", vals[0], ". Possible values: ", currentStruct.functions, defReader);
499 					GirParam param = findParam(currentStruct, vals[0], vals[1]);
500 					if ( !param )
501 						error("Unknown parameter ", vals[1], " in function ", vals[0]);
502 					param.direction = GirParamDirection.InOut;
503 					break;
504 
505 				default:
506 					error("Unknown key: ", defReader.key, defReader);
507 			}
508 
509 			defReader.popFront();
510 		}
511 	}
512 
513 	void proccessGIR(string girFile)
514 	{
515 		GirPackage pack = new GirPackage("", this, srcDir);
516 
517 		if ( !isAbsolute(girFile) )
518 		{
519 			girFile = getAbsoluteGirPath(girFile);
520 		}
521 
522 		pack.parseGIR(girFile);
523 		packages[pack.name] = pack;
524 	}
525 
526 	void printFreeFunctions()
527 	{
528 		foreach ( pack; packages )
529 		{
530 			foreach ( func; pack.collectedFunctions )
531 			{
532 				if ( func.movedTo.empty )
533 					writefln("%s: %s", pack.name, func.name);
534 			}
535 		}
536 	}
537 
538 	void writeFile(string fileName, string contents, bool createDirectory = false)
539 	{
540 		if ( createDirectory )
541 		{
542 			try
543 			{
544 				if ( !exists(fileName.dirName()) )
545 					mkdirRecurse(fileName.dirName());
546 			}
547 			catch (FileException ex)
548 			{
549 				error("Failed to create directory: ", ex.msg);
550 			}
551 		}
552 
553 		std.file.write(fileName, contents);
554 
555 		if ( printFiles )
556 			printFilePath(fileName);
557 	}
558 
559 	string getAbsoluteGirPath(string girFile)
560 	{
561 		if ( commandlineGirPath )
562 		{
563 			string cmdGirFile = buildNormalizedPath(commandlineGirPath, girFile);
564 
565 			if ( exists(cmdGirFile) )
566 				return cmdGirFile;
567 		}
568 
569 		return buildNormalizedPath(getGirDirectory(), girFile);
570 	}
571 
572 	private void printFilePath(string fileName)
573 	{
574 		with (PrintFileMethod) switch(printFileMethod)
575 		{
576 			case Absolute:
577 				writeln(asAbsolutePath(fileName));
578 				break;
579 			case Relative:
580 				writeln(asRelativePath(asAbsolutePath(fileName), cwdOrBaseDirectory));
581 				break;
582 			default:
583 				writeln(fileName);
584 				break;
585 		}
586 	}
587 
588 	private string getGirDirectory()
589 	{
590 		version(Windows)
591 		{
592 			import std.process : environment;
593 
594 			static string path;
595 
596 			if (path !is null)
597 				return path;
598 
599 			foreach (p; splitter(environment.get("PATH"), ';'))
600 			{
601 				string dllPath = buildNormalizedPath(p, "libgtk-3-0.dll");
602 
603 				if ( exists(dllPath) )
604 					path = p.buildNormalizedPath("../share/gir-1.0");
605 			}
606 
607 			return path;
608 		}
609 		else version(OSX)
610 		{
611 			import std.process : environment;
612 
613 			static string path;
614 
615 			if (path !is null)
616 				return path;
617 
618 			path = environment.get("GTK_BASEPATH");
619 			if(path)
620 			{
621 				path = path.buildNormalizedPath("../share/gir-1.0");
622 			}
623 			else
624 			{
625 				path = environment.get("HOMEBREW_ROOT");
626 				if(path)
627 				{
628 					path = path.buildNormalizedPath("share/gir-1.0");
629 				}
630 			}
631 
632 			return path;
633 		}
634 		else
635 		{
636 			return "/usr/share/gir-1.0";
637 		}
638 	}
639 
640 	private GirParam findParam(GirStruct strct, string func, string name)
641 	{
642 		foreach( param; strct.functions[func].params )
643 		{
644 			if ( param.name == name )
645 				return param;
646 		}
647 
648 		return null;
649 	}
650 
651 	private void loadAA (ref string[string] aa, const DefReader defReader)
652 	{
653 		string[] vals = defReader.value.split();
654 
655 		if ( vals.length == 1 )
656 			vals ~= "";
657 
658 		if ( vals.length == 2 )
659 			aa[vals[0]] = vals[1];
660 		else
661 			error("Worng amount of arguments for key: ", defReader.key, defReader);
662 	}
663 
664 	private void loadDependency(DefReader defReader)
665 	{
666 		if ( defReader.value == "end" )
667 			return;
668 
669 		if ( defReader.subKey.empty )
670 			error("No dependency specified.", defReader);
671 
672 		GirInclude inc = GirPackage.includes.get(defReader.subKey, GirInclude.init);
673 
674 		if ( defReader.value == "skip" )
675 			inc.skip = true;
676 		else if ( defReader.value == "start" )
677 		{
678 			inc.lookupFile = defReader.fileName;
679 			inc.lookupLine = defReader.lineNumber;
680 
681 			inc.lookupText = defReader.readBlock();
682 		}
683 		else
684 			error("Missing 'skip' or 'start' for dependency: ", defReader.subKey, defReader);
685 
686 		GirPackage.includes[defReader.subKey] = inc;
687 	}
688 
689 	private void copyFiles(string srcDir, string destDir, string file)
690 	{
691 		string from = buildNormalizedPath(srcDir, file);
692 		string to = buildNormalizedPath(destDir, file);
693 
694 		if ( !printFiles )
695 			writefln("copying file [%s] to [%s]", from, to);
696 
697 		if ( isFile(from) )
698 		{
699 			if ( printFiles )
700 				writeln(to);
701 
702 			copy(from, to);
703 			return;
704 		}
705 
706 		void copyDir(string from, string to)
707 		{
708 			if ( !exists(to) )
709 				mkdirRecurse(to);
710 
711 			foreach ( entry; dirEntries(from, SpanMode.shallow) )
712 			{
713 				string dst = buildPath(to, entry.name.baseName);
714 
715 				if ( isDir(entry.name) )
716 				{
717 					copyDir(entry.name, dst);
718 				}
719 				else
720 				{
721 					if ( printFiles && !dst.endsWith("functions-runtime.d") && !dst.endsWith("functions-compiletime.d") )
722 						printFilePath(dst);
723 						
724 					copy(entry.name, dst);
725 				}
726 			}
727 		}
728 
729 		copyDir(from, to);
730 
731 		if ( file == "cairo" )
732 		{
733 			if ( printFiles )
734 				printFilePath(buildNormalizedPath(to, "c", "functions.d"));
735 
736 			if ( useRuntimeLinker )
737 				copy(buildNormalizedPath(to, "c", "functions-runtime.d"), buildNormalizedPath(to, "c", "functions.d"));
738 			else
739 				copy(buildNormalizedPath(to, "c", "functions-compiletime.d"), buildNormalizedPath(to, "c", "functions.d"));
740 
741 			remove(buildNormalizedPath(to, "c", "functions-runtime.d"));
742 			remove(buildNormalizedPath(to, "c", "functions-compiletime.d"));
743 		}
744 	}
745 
746 	private GirStruct createClass(GirPackage pack, string name)
747 	{
748 		GirStruct strct = new GirStruct(this, pack);
749 		strct.name = name;
750 		strct.cType = pack.cTypePrefix ~ name;
751 		strct.type = GirStructType.Record;
752 		strct.noDeclaration = true;
753 		pack.collectedStructs["lookup"~name] = strct;
754 
755 		return strct;
756 	}
757 
758 	private bool checkOsVersion(string _version)
759 	{
760 		if ( _version.empty || !(_version[0].isAlpha() || _version[0] == '!') )
761 			return false;
762 
763 		version(Windows)
764 		{
765 			return _version.among("Windows", "!OSX", "!linux", "!Linux", "!Posix") != 0;
766 		}
767 		else version(OSX)
768 		{
769 			return _version.among("!Windows", "OSX", "!linux", "!Linux", "Posix") != 0;
770 		}
771 		else version(linux)
772 		{
773 			return _version.among("!Windows", "!OSX", "linux", "Linux", "Posix") != 0;
774 		}
775 		else version(Posix)
776 		{
777 			return _version.among("!Windows", "!OSX", "!linux", "!Linux", "Posix") != 0;
778 		}
779 		else
780 		{
781 			return false;
782 		}
783 	}
784 
785 }
786 
787 /**
788  * Apply aliasses to the tokens in the string, and
789  * camelCase underscore separated tokens.
790  */
791 string stringToGtkD(string str, string[string] aliases, bool caseConvert = true)
792 {
793 	return stringToGtkD(str, aliases, null, caseConvert);
794 }
795 
796 string stringToGtkD(string str, string[string] aliases, string[string] localAliases, bool caseConvert = true)
797 {
798 	size_t pos, start;
799 	string seps = " \n\r\t\f\v()[]*,;";
800 	auto converted = appender!string();
801 
802 	while ( pos < str.length )
803 	{
804 		if ( !seps.canFind(str[pos]) )
805 		{
806 			start = pos;
807 
808 			while ( pos < str.length && !seps.canFind(str[pos]) )
809 				pos++;
810 
811 			//Workaround for the tm struct, type and variable have the same name.
812 			if ( pos < str.length && str[pos] == '*' && str[start..pos] == "tm" )
813 				converted.put("void");
814 			else
815 				converted.put(tokenToGtkD(str[start..pos], aliases, localAliases, caseConvert));
816 
817 			if ( pos == str.length )
818 				break;
819 		}
820 
821 		converted.put(str[pos]);
822 		pos++;
823 	}
824 
825 	return converted.data;
826 }
827 
828 unittest
829 {
830 	assert(stringToGtkD("token", ["token":"tok"]) == "tok");
831 	assert(stringToGtkD("string token_to_gtkD(string token, string[string] aliases)", ["token":"tok"])
832 	       == "string tokenToGtkD(string tok, string[string] aliases)");
833 }
834 
835 string tokenToGtkD(string token, string[string] aliases, bool caseConvert=true)
836 {
837 	return tokenToGtkD(token, aliases, null, caseConvert);
838 }
839 
840 string tokenToGtkD(string token, string[string] aliases, string[string] localAliases, bool caseConvert=true)
841 {
842 	if ( token in glibTypes )
843 		return glibTypes[token];
844 	else if ( token in localAliases )
845 		return localAliases[token];
846 	else if ( token in aliases )
847 		return aliases[token];
848 	else if ( token.endsWith("_t", "_t*", "_t**") )
849 		return token;
850 	else if ( token == "pid_t" || token == "size_t" )
851 		return token;
852 	else if ( caseConvert )
853 		return tokenToGtkD(removeUnderscore(token), aliases, localAliases, false);
854 	else
855 		return token;
856 }
857 
858 string removeUnderscore(string token)
859 {
860 	char pc;
861 	auto converted = appender!string();
862 
863 	while ( !token.empty )
864 	{
865 		if ( token[0] == '_' )
866 		{
867 			pc = token[0];
868 			token = token[1..$];
869 
870 			continue;
871 		}
872 
873 		if ( pc == '_' )
874 			converted.put(token[0].toUpper());
875 		else
876 			converted.put(token[0]);
877 
878 		pc = token[0];
879 		token = token[1..$];
880 	}
881 
882 	return converted.data;
883 }
884 
885 unittest
886 {
887 	assert(removeUnderscore("this_is_a_test") == "thisIsATest");
888 }