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 }