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 GtkType elementType = new GtkType(this); 343 344 elementType.name = func.returnType.name; 345 elementType.cType = func.returnType.cType[0..$-1]; 346 func.returnType.elementType = elementType; 347 348 foreach( i, p; func.params ) 349 { 350 if ( p.name == vals[2] ) 351 func.returnType.length = cast(int)i; 352 } 353 } 354 else 355 { 356 GtkParam param = findParam(currentStruct, vals[0], vals[1]); 357 GtkType elementType = new GtkType(this); 358 359 elementType.name = param.type.name; 360 elementType.cType = param.type.cType[0..$-1]; 361 param.type.elementType = elementType; 362 363 if ( vals.length < 3 ) 364 { 365 param.type.zeroTerminated = true; 366 break; 367 } 368 369 if ( vals[2] == "Return" ) 370 { 371 param.type.length = -2; 372 break; 373 } 374 375 foreach( i, p; func.params ) 376 { 377 if ( p.name == vals[2] ) 378 param.type.length = cast(int)i; 379 } 380 } 381 break; 382 case "copy": 383 if ( srcDir.empty ) 384 throw new WrapError(defReader, 385 "Can't copy the file when srcDir is not set"); 386 387 copyFile(apiRoot, buildPath(outputRoot, srcDir), defReader.value); 388 break; 389 default: 390 throw new WrapError(defReader, "Unknown key: "~defReader.key); 391 } 392 393 defReader.popFront(); 394 } 395 } 396 397 void printFreeFunctions() 398 { 399 foreach ( pack; packages ) 400 { 401 foreach ( func; pack.collectedFunctions ) 402 { 403 if ( func.movedTo.empty ) 404 writefln("%s: %s", pack.name, func.name); 405 } 406 } 407 } 408 409 private GtkParam findParam(GtkStruct strct, string func, string name) 410 { 411 foreach( param; strct.functions[func].params ) 412 { 413 if ( param.name == name ) 414 return param; 415 } 416 417 return null; 418 } 419 420 private void loadAA (ref string[string] aa, DefReader defReader) 421 { 422 string[] vals = defReader.value.split(); 423 424 if ( vals.length == 1 ) 425 vals ~= ""; 426 427 if ( vals.length == 2 ) 428 aa[vals[0]] = vals[1]; 429 else 430 throw new WrapError(defReader, "Unknown key: "~defReader.key); 431 } 432 433 private void copyFile(string srcDir, string destDir, string file) 434 { 435 string from = buildPath(srcDir, file); 436 string to = buildPath(destDir, file); 437 438 writefln("copying file [%s] to [%s]", from, to); 439 copy(from, to); 440 441 if ( !exists(to) ) 442 throw new Exception("Cannot copy file: "~from); 443 } 444 445 private GtkStruct createClass(GtkPackage pack, string name) 446 { 447 GtkStruct strct = new GtkStruct(this, pack); 448 strct.name = name; 449 strct.cType = pack.cTypePrefix ~ name; 450 strct.type = GtkStructType.Record; 451 strct.noDecleration = true; 452 pack.collectedStructs["lookup"~name] = strct; 453 454 return strct; 455 } 456 } 457 458 /** 459 * Apply aliasses to the tokens in the string, and 460 * camelCase underscore separated tokens. 461 */ 462 string stringToGtkD(string str, string[string] aliases, string[string] localAliases = null) 463 { 464 size_t pos, start; 465 string seps = " \n\r\t\f\v()[]*,;"; 466 auto converted = appender!string(); 467 468 while ( pos < str.length ) 469 { 470 if ( !seps.canFind(str[pos]) ) 471 { 472 start = pos; 473 474 while ( pos < str.length && !seps.canFind(str[pos]) ) 475 pos++; 476 477 //Workaround for the tm struct, type and variable have the same name. 478 if ( pos < str.length && str[pos] == '*' && str[start..pos] == "tm" ) 479 converted.put("void"); 480 else 481 converted.put(tokenToGtkD(str[start..pos], aliases, localAliases)); 482 483 if ( pos == str.length ) 484 break; 485 } 486 487 converted.put(str[pos]); 488 pos++; 489 } 490 491 return converted.data; 492 } 493 494 unittest 495 { 496 assert(stringToGtkD("token", ["token":"tok"]) == "tok"); 497 assert(stringToGtkD("string token_to_gtkD(string token, string[string] aliases)", ["token":"tok"]) 498 == "string tokenToGtkD(string tok, string[string] aliases)"); 499 } 500 501 string tokenToGtkD(string token, string[string] aliases, bool caseConvert=true) 502 { 503 return tokenToGtkD(token, aliases, null, caseConvert); 504 } 505 506 string tokenToGtkD(string token, string[string] aliases, string[string] localAliases, bool caseConvert=true) 507 { 508 if ( token in localAliases ) 509 return localAliases[token]; 510 else if ( token in aliases ) 511 return aliases[token]; 512 else if ( token.startsWith("cairo_") && token.endsWith("_t", "_t*", "_t**") ) 513 return token; 514 else if ( token == "pid_t" ) 515 return token; 516 else if ( caseConvert ) 517 return tokenToGtkD(removeUnderscore(token), aliases, localAliases, false); 518 else 519 return token; 520 } 521 522 string removeUnderscore(string token) 523 { 524 char pc; 525 auto converted = appender!string(); 526 527 while ( !token.empty ) 528 { 529 if ( token[0] == '_' ) 530 { 531 pc = token[0]; 532 token = token[1..$]; 533 534 continue; 535 } 536 537 if ( pc == '_' ) 538 converted.put(token[0].toUpper()); 539 else 540 converted.put(token[0]); 541 542 pc = token[0]; 543 token = token[1..$]; 544 } 545 546 return converted.data; 547 } 548 549 unittest 550 { 551 assert(removeUnderscore("this_is_a_test") == "thisIsATest"); 552 }