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