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.HTODConvert;
21 
22 private import utils.DefReader;
23 private import utils.GtkWrapper;
24 
25 private import std.stdio;
26 private import std.file;
27 import std.path;
28 private import std.string;
29 private import std.process;
30 
31 static string htod_location = "wrap/utils/htod.exe";
32 static string wine_command = "wine";
33 
34 // there is no longer a 'bit' type
35 alias bool bit;
36 
37 //debug = flow;
38 //debug = processLine;
39 //debug = getDType;
40 //debug = functions;
41 
42 public class Ranges
43 {
44 
45 	private struct Range
46 	{
47 		int vStart;
48 		int vEnd;
49 		bool exclude = true;
50 		
51 		public bool contains(int pos)
52 		{
53 			return (pos>= vStart) && (pos<=vEnd);
54 		}
55 		
56 		public bool includes(int pos)
57 		{
58 			return contains(pos)
59 				? !exclude
60 				: exclude
61 				;
62 		}
63 
64 		public bool matches(int start, int end)
65 		{
66 			return vStart == start 
67 				&& vEnd == end;
68 		}
69 	}
70 	
71 	private Range*[] ranges;
72 	
73 	public void addRange(int start, int end, bool exclude)
74 	{
75 		Range* range = new Range;
76 		range.vStart = start;
77 		range.vEnd = end;
78 		range.exclude = exclude;
79 		ranges ~= range;
80 	}
81 	
82 	/**
83 	 * Finds the last range that contains the position and returns it's include value
84 	 * Params:
85 	 *    	pos = 	
86 	 */
87 	public void include(int pos)
88 	{
89 		bool incl = true;
90 		Range* range;
91 		size_t i = ranges.length;
92 		while ( i > 0 )
93 		{
94 			range = ranges[--i];
95 			if ( range.contains(pos) )
96 			{
97 				i=0;
98 				incl = !range.exclude;
99 			}
100 		}
101 		//return incl;
102 	}
103 	
104 }
105 
106 /**
107  * This is used to post-process dm htod .d file for gtkD
108  * to create the htod .d file:
109  * - copy the original .h file to a working directory
110  * - remove all unecessary definitions
111  * - run `wine ~/dm/bin/htod.exe <file.h>`
112  * - run this on the generated .d file
113  */
114 public class HTODConvert
115 {
116 	string dText;
117 	
118 	DefReader defReader;
119 
120 	/** the library id on the system (for instance GL for libGL.so) */
121 	string lib;
122 	/** the .h (or .h pos-processed) to convert to .d */
123 	string preFile;
124 	/** the package */
125 	string pack;
126 	string bindDir;
127 	string outputRoot;
128 	string inputRoot;
129 	/** convert to dynamic loading */
130 	bool dynLoad;
131 	/** mark when the header was already added to the text */
132 	bool dynLoadAlreadyOpen;
133 	/** mar when the module and license where already added to the text */
134 	bool headerAlreadyAdded;
135 	string[] functions;
136 	string[] comment;
137 	
138 	
139 	this(string htodFilename)
140 	{
141 		defReader = new DefReader(htodFilename);
142 		clearValues();
143 		process();
144 	}
145 	this(string htodFilename, string _outputRoot, string _inputRoot)
146 	{
147 		inputRoot = _inputRoot;
148 		outputRoot = _outputRoot;
149 		this(htodFilename);
150 	}	
151 	void process()
152 	{
153 		debug(flow)(writefln("HTODConvert.process 1"));
154 		while ( defReader.next().length > 0 )
155 		{
156 			switch ( defReader.getKey() )
157 			{
158 				case "prefile":
159 					debug(flow)(writefln("HTODConvert.process case prefile"));
160 					preFile = defReader.getValue();
161 					break;
162 				case "bindDir":
163 					bindDir = defReader.getValue();
164 				break;	
165 				case "pack":
166 					debug(flow)(writefln("HTODConvert.process case pack"));
167 					pack = defReader.getValue();
168 					break;
169 					
170 				case "lib":
171 					debug(flow)(writefln("HTODConvert.process case libe"));
172 					lib = defReader.getValue();
173 					break;
174 					
175 				case "file":
176 					debug(flow)(writefln("HTODConvert.process case file 1"));
177 					if ( preFile.length > 0 )
178 					{
179 						debug(flow)(writefln("HTODConvert.process case file 2"));
180 						processPreFile(preFile, defReader.getValue());
181 						debug(flow)(writefln("HTODConvert.process case file 3"));
182 					}
183 					debug(flow)(writefln("HTODConvert.process case file 4"));
184 					preFile.length = 0;
185 					debug(flow)(writefln("HTODConvert.process case file 5"));
186 					processFile(defReader.getValue());
187 					debug(flow)(writefln("HTODConvert.process case file 6 "));
188 					break;
189 				
190 				case "dynload":
191 					debug(flow)(writefln("HTODConvert.process case dynload"));
192 					dynLoadAlreadyOpen = false;
193 					headerAlreadyAdded = false;
194 					dynLoad = true;
195 					break;
196 					
197 				case "outfile":
198 					debug(flow)(writefln("HTODConvert.process case outfile"));
199 					writeFile(defReader.getValue());
200 					clearValues();
201 					break;
202 					
203 				default:
204 					debug(flow)(writefln("HTODConvert.process case default"));
205 					writefln("Unknown key/value = %s/%s", defReader.getKey(), defReader.getValue());
206 					break;
207 					
208 			}
209 		}
210 	}
211 
212 	void processPreFile(string preFileName, string fileName)
213 	{
214 		//debug(flow)
215 		writefln("HTODConvert.processPreFile files: %s > %s", preFileName, fileName);
216 		string[] args;
217 		args ~= htod_location;
218 		args ~= htod_location;
219 		args ~= preFileName;
220 		args ~= std.path.buildPath(inputRoot,fileName);
221 		try
222 		{
223 			std.process.execvp(wine_command, args);
224 		}
225 		catch (Throwable e)
226 		{
227 			// ignore - it always fail - most of the time produces the file
228 		}
229 	}
230 
231 	void processFile(string fileName)
232 	{
233 		debug(flow)(writefln("HTODConvert.processFile"));
234 		string hText = cast(string)std.file.read(fileName);
235 		string[] hLines = std..string.splitLines(hText);
236 		string line;
237 
238 		int i = 0;
239 		while ( i < hLines.length )
240 		{
241 			line = hLines[i++];
242 			if ( std..string.indexOf(line,'(')>=0 )
243 			{
244 				int openBrace = 1;
245 				if ( std..string.indexOf(line,')')>=0 ) --openBrace;
246 				while ( openBrace > 0
247 					&&  i<hLines.length 
248 					)
249 				{
250 					string l = hLines[i++];
251 					if ( std..string.indexOf(l,')') >=0) --openBrace;
252 					line ~= " " ~ std..string.strip(l);
253 				}
254 			}
255 			addLine(line);
256 		}
257 		
258 		if ( dynLoad )
259 		{
260 			closeDynLoad();
261 		}
262 	}
263 	
264 	void addLine(string line)
265 	{
266 		if ( !startsWith(line, "//C") 
267 			&& line !="alias extern GLAPI;"
268 			&& line !="alias GLAPIENTRY APIENTRY;"
269 			)
270 		{
271 			if ( line == "alias sbyte GLbyte;" )
272 			{
273 				line = "alias byte GLbyte;";
274 			}
275 			if ( line == "module wrap/cHeaders/GL/gl;" )
276 			{
277 				line = "module "~bindDir~".gl;";
278 			}
279 			
280 			if ( line == "module wrap/cHeaders/GL/glu;" )
281 			{
282 				line = "module "~bindDir~".glu;";
283 			}
284 
285 			if ( line == "import std.c.gl;" )
286 			{
287 				line = "import "~bindDir~".gl;";
288 			}
289 
290 			if ( startsWith(line, "/* Converted to D from" ) )
291 			{
292 				line = GtkWrapper.license;
293 			}
294 			
295 			if ( dynLoad )
296 			{
297 				addLineDynLoad(line);
298 			}
299 			else
300 			{
301 				dText ~= line~"\n";
302 			}
303 			
304 		}
305 	}
306 
307 	/**
308 	 * If the line is a function definition stores the line to include on the extern(C)
309 	 * and the symbols.
310 	 * for now a function is a line that ends with ");"
311 	 * Params:
312 	 *    	dText = 	
313 	 *    	line = 	
314 	 */
315 	void addLineDynLoad(string line)
316 	{
317 		//debug(flow)(writefln("HTODConvert.addLineDynLoad"));
318 		
319 		if ( headerAlreadyAdded
320 			&& !dynLoadAlreadyOpen 
321 			)
322 		{
323 			openDynLoad();
324 		}
325 
326 		if ( endsWith(line, ");") )
327 		{
328 			int i=0;
329 			while ( i<comment.length )
330 			{
331 				functions ~= comment[i++];
332 			}
333 			comment.length = 0;
334 			functions ~= line;
335 			debug(functions)writefln("HTODConvert.addLineDynLoad function[%s] = %s",
336 								functions.length, line);
337 		}
338 		else if ( startsWith(line, "/*")
339 			|| startsWith(line, " *")
340 			|| startsWith(line, " */")
341 			|| line.length == 0
342 			)
343 		{
344 			comment ~= line;
345 		}
346 		else
347 		{
348 			if ( startsWith(line, "module") )
349 			{
350 				headerAlreadyAdded = true;
351 			}
352 			int i=0;
353 			while ( i<comment.length )
354 			{
355 				dText ~= comment[i++]~"\n";
356 			}
357 			comment.length = 0;
358 			dText ~= line~"\n";
359 		}
360 	}
361 	
362 	void openDynLoad()
363 	{
364 		debug(flow)(writefln("HTODConvert.openDynLoad"));
365 		dynLoadAlreadyOpen = true;
366 		dText ~= 
367 "\n"
368 "\n"
369 "private import std.stdio;\n"
370 "private import "~bindDir~"."~pack~"types;\n"
371 "private import gtkc.Loader;\n"
372 "private import gtkc.paths;\n"
373 "\n"
374 ;
375 		if ( lib == "GL"
376 			 || lib == "GLU" )
377 		{
378 			return;
379 		}
380 		dText ~= 
381 "private Linker "~lib~"_Linker;\n"
382 "\n"
383 "static this()\n"
384 "{\n"
385 "	"~lib~"_Linker = new Linker(libPath ~ importLibs[LIBRARY."~std..string.toUpper(lib)~"] );\n"
386 "	"~lib~"_Linker.link("~lib~"Links);\n"
387 "	debug writefln(\"* Finished static this(): "~lib~"\");\n"
388 "}\n"
389 "\n"
390 "static ~this()\n"
391 "{\n"
392 "	delete "~lib~"_Linker;\n"
393 "	debug writefln(\"* Finished static ~this(): "~lib~"\");\n"
394 "}\n\n"
395 ;
396 
397 	
398 	}
399 	
400 	void closeDynLoad()
401 	{
402 		debug(flow)(writefln("HTODConvert.closeDynLoad"));
403 		dText ~= "\n\nextern(C)\n{\n";
404 		foreach ( string line ; functions )
405 		{
406 			dText ~= "\t" ~ line ~"\n";
407 		}
408 		dText ~= "} // extern(C)\n";
409 		
410 		dText ~= "\n\nSymbol[] "~lib~"Links = \n";
411 		dText ~= "[\n";
412 		
413 		foreach ( string line ; functions )
414 		{
415 			if ( startsWith(line, "/*")
416 				|| startsWith(line, " *")
417 				|| startsWith(line, " */")
418 				|| line.length == 0
419 				)
420 			{
421 				dText ~= "\t"~line~"\n";
422 			}
423 			else
424 			{
425 				sizediff_t end = std..string.indexOf(line,'(');
426 				
427 				if ( end > 0 )
428 				{
429 					sizediff_t start = end;
430 					while ( start > 0 && line[start] > ' ' )
431 					{
432 						--start;
433 					}
434 					string functionName = line[start+1..end];
435 					if ( functionName.length>1)
436 					{
437 						dText ~= "\t{ \""
438 							~functionName
439 							~"\",  cast(void**)& "
440 							~functionName
441 							~"},\n"
442 							;
443 					}
444 				}
445 			}
446 
447 		}
448 		
449 		dText ~= "];\n\n";
450 	}
451 	
452 	void clearValues()
453 	{
454 		debug(flow)(writefln("HTODConvert.clearValues"));
455 		dText.length = 0;
456 		dynLoad = false;
457 		lib.length = 0;
458 		preFile.length = 0;
459 		pack.length = 0;
460 		bool dynLoadAlreadyOpen = false;
461 		bool headerAlreadyAdded = false;
462 		functions.length = 0;
463 		comment.length = 0;
464 	
465 	}
466 	
467 	void writeFile(string fileName)
468 	{
469 		debug(flow)(writefln("HTODConvert.writeFile"));
470 		debug(flow)(writefln("HTODConvert.writeFile fileName = %s", fileName));
471 		std.file.write(std.path.buildPath(outputRoot,fileName), dText);
472 	}
473 	
474 	public static bit startsWith(string str, string prefix)
475 	{
476 		return str.length >= prefix.length 
477 				&& str[0..prefix.length] == prefix;
478 	}
479 	
480 	public static bit startsWith(string str, char prefix)
481 	{
482 		return str.length > 0
483 				&& str[0] == prefix;
484 	}
485 	
486 	public static bit endsWith(string str, string prefix)
487 	{
488 		return str.length >= prefix.length 
489 				&& str[str.length-prefix.length..str.length] == prefix;
490 	}
491 	
492 	public static bit endsWith(string str, char suffix)
493 	{
494 		return str.length >= 1
495 				&& str[str.length-1] == suffix;
496 	}
497 	
498 }
499 
500