1 2 // Copyright Marcelo S. N. Mancini(Hipreme) 2020. 3 // Distributed under the Boost Software License, Version 1.0. 4 // (See accompanying file LICENSE_1_0.txt or copy at 5 // http://www.boost.org/LICENSE_1_0.txt) 6 7 module generate; 8 import regexes; 9 import std.file; 10 import std.process:executeShell; 11 import std.array; 12 import std..string; 13 import std.getopt; 14 import std.regex : replaceAll, matchAll, regex, Regex; 15 import std.path:baseName, stripExtension; 16 import std.stdio:writeln, File; 17 import pluginadapter; 18 import plugin; 19 20 21 enum D_TO_REPLACE 22 { 23 loneVoid = "()", 24 unsigned_int = "uint", 25 unsigned_char = "ubyte", 26 _string = "const (char)*", //This will be on the final as it causes problem on regexes 27 head_const = "const $1", 28 _callback = "$1 function($3) $2", 29 _in = " in_", 30 _out = " out_", 31 _align = " align_", 32 _ref = " ref_", 33 //C++ part 34 _template = "$2!($1)", 35 address = "ref $1", 36 NULL = " null", 37 38 _struct = "", 39 _array = "$1* $2", 40 _nullAddress = " null" 41 } 42 43 enum AliasCreation = "alias p$2 = $1 function"; 44 enum GSharedCreation = "p$2 $2"; 45 enum BindSymbolCreation = "lib.bindSymbol(cast(void**)&$2, \"$2\");"; 46 47 48 49 File createDppFile(string file) 50 { 51 File f; 52 string dppFile = baseName(stripExtension(file)) ~ ".dpp"; 53 if(!exists(file)) 54 { 55 writeln("File does not exists"); 56 return f; 57 } 58 if(lastIndexOf(file, ".h") == -1) 59 { 60 writeln("File must be a header"); 61 return f; 62 } 63 if(!exists(dppFile)) 64 { 65 f = File(dppFile, "w"); 66 f.write("#include \""~file~"\""); 67 writeln("File '" ~ dppFile ~ "' created"); 68 } 69 else 70 { 71 f = File(dppFile); 72 writeln("File '" ~ dppFile ~ "' already exists, ignoring content creation"); 73 } 74 return f; 75 } 76 77 bool executeDpp(File file, string _dppArgs) 78 { 79 string[4] tests = ["d++", "d++.exe", "dpp", "dpp.exe"]; 80 string selected; 81 foreach(t; tests) 82 { 83 if(exists(t)) 84 { 85 selected = t; 86 break; 87 } 88 } 89 if(selected == "") 90 { 91 writeln("Could not create types.d\nReason: d++/dpp is not on the current folder"); 92 return false; 93 } 94 95 string[] dppArgs = [selected, "--preprocess-only"]; 96 if(_dppArgs != "") 97 dppArgs~= _dppArgs.split(","); 98 dppArgs~=file.name; 99 100 auto ret = executeShell(dppArgs.join(" ")); 101 102 103 //Okay 104 if(ret.status == 0) 105 { 106 writeln("Types.d was succesfully created with "~dppArgs.join(" ")); 107 //Instead of renaming, just copy its content and delete it 108 string noExt = stripExtension(file.name); 109 string genName = noExt~".d"; 110 string fileContent = "module bindbc."~ noExt~".types;\n" ~readText(genName); 111 mkdirRecurse("bindbc/cimgui"); 112 std.file.write("bindbc/"~noExt~"/types.d", fileContent); 113 } 114 else 115 { 116 writeln(r" 117 Could not execute dpp: 118 DPP output 119 -------------------------------------------------- 120 "); 121 writeln(ret.output); 122 writeln(r" 123 -------------------------------------------------- 124 End of Dpp output 125 "); 126 } 127 return true; 128 } 129 130 /** 131 * Get every function from file following the regex, the regex should only match 132 */ 133 auto getFuncs(Input, Reg)(Input file, Reg reg) 134 { 135 if(lastIndexOf(file, ".h") == -1) 136 return ""; 137 138 writeln("Getting file '"~file~"' functions"); 139 string f = readText(file); 140 auto matches = matchAll(f, reg); 141 string ret; 142 foreach(m; matches) 143 { 144 ret~= m.hit~"\n"; 145 } 146 return ret; 147 } 148 149 /** 150 * Uses a bunch of presets written in the file head, it will convert every C func 151 * declaration to D, arrays are transformed to pointers, as if it becomes ref, the function 152 * won't be able to accept casts 153 */ 154 string cppFuncsToD(string funcs, bool replaceAditional = false) 155 { 156 alias f = funcs; 157 writeln("Converting functions to D style"); 158 with(D_TO_REPLACE) 159 { 160 f = f.replaceAll(CPP_TO_D.replaceUint, unsigned_int); 161 f = f.replaceAll(CPP_TO_D.replaceUByte, unsigned_char); 162 f = f.replaceAll(CPP_TO_D.replaceCallback, _callback); 163 f = f.replaceAll(CPP_TO_D.replaceIn, _in); 164 f = f.replaceAll(CPP_TO_D.replaceOut, _out); 165 f = f.replaceAll(CPP_TO_D.replaceAlign, _align); 166 f = f.replaceAll(CPP_TO_D.replaceRef, _ref); 167 //C++ Part 168 f = f.replaceAll(CPP_TO_D.replaceTemplate, _template); 169 f = f.replaceAll(CPP_TO_D.replaceAddress, address); 170 f = f.replaceAll(CPP_TO_D.replaceNULL, NULL); 171 172 f = f.replaceAll(CPP_TO_D.replaceStruct, _struct); 173 f = f.replaceAll(CPP_TO_D.replaceArray, _array); 174 f = f.replaceAll(CPP_TO_D.replaceNullAddress, _nullAddress); 175 f = f.replaceAll(CPP_TO_D.removeLoneVoid, loneVoid ); 176 177 if(replaceAditional) 178 { 179 f = f.replaceAll(CPP_TO_D.replaceString, _string); 180 f = f.replaceAll(CPP_TO_D.replaceHeadConst, head_const); 181 } 182 } 183 return funcs; 184 } 185 186 auto cleanPreFuncsDeclaration(Strs, Reg)(Strs funcs, Reg reg) 187 { 188 writeln("Cleaning pre function declaration"); 189 funcs = replaceAll(funcs, reg, "$1"); 190 return funcs; 191 } 192 193 string[] getFuncNames(string[] funcs) 194 { 195 writeln("Getting function names"); 196 foreach(ref f; funcs) 197 f = f.replaceAll(GetFuncParamsAndName2, "$2"); 198 return funcs; 199 } 200 201 string generateAliases(string[] funcs) 202 { 203 string ret = ""; 204 foreach(f; funcs) 205 { 206 string buf = f.replaceAll(GetFuncParamsAndName2, "alias da_$2 = $1 function $3;\n\t"); 207 buf = buf.replaceAll(CPP_TO_D.replaceString, D_TO_REPLACE._string); 208 buf = buf.replaceAll(CPP_TO_D.replaceHeadConst, D_TO_REPLACE.head_const); 209 ret~=buf; 210 } 211 return ret; 212 } 213 214 string generateGSharedFuncs(string[] funcs) 215 { 216 string ret = "\n__gshared\n{\t"; 217 218 size_t len = funcs.length-1; 219 foreach(i, f; funcs) 220 { 221 if(f != "") 222 { 223 ret~= "da_"~f~" "~f~";\n"; 224 if(i + 1 != len) 225 ret~="\t"; 226 } 227 } 228 229 ret~="}"; 230 return ret; 231 } 232 233 void createFuncsFile(string libName, ref string[] funcNames) 234 { 235 writeln("Writing funcs.d"); 236 string fileContent = q{ 237 module bindbc.$.funcs; 238 import bindbc.$.types; 239 import core.stdc.stdarg:va_list; 240 241 extern(C) @nogc nothrow 242 }.replaceAll(DollarToLib, libName); 243 fileContent~="{\n\t"; 244 fileContent~=generateAliases(funcNames); 245 fileContent~="\n}"; 246 247 funcNames = getFuncNames(funcNames); 248 fileContent~=generateGSharedFuncs(funcNames); 249 250 mkdirRecurse("bindbc/"~libName); 251 std.file.write("./bindbc/"~libName~"/funcs.d", fileContent); 252 } 253 254 /** 255 * 256 */ 257 string generateBindSymbols(string[] funcs) 258 { 259 string ret = ""; 260 size_t len = funcs.length-1; 261 foreach(i, f; funcs) 262 { 263 if(f != "") 264 ret~= "lib.bindSymbol(cast(void**)&"~f~", \""~f~"\");\n"; 265 if(i + 1 != len) 266 ret~="\t"; 267 } 268 return ret; 269 } 270 271 272 /** 273 * Create library loading file named libNameload.d 274 */ 275 void createLibLoad(string libName, string[] funcNames) 276 { 277 writeln("Writing "~libName~"load.d"); 278 string fileContent = "module bindbc."~libName~"."~libName~"load;\n"; 279 fileContent~="import bindbc.loader;\n"; 280 fileContent~="import bindbc."~libName~".types;\n"; 281 fileContent~="import bindbc."~libName~".funcs;\n"; 282 fileContent~="private\n{\n\tSharedLib lib;\n}"; 283 fileContent~=` 284 bool load$() 285 { 286 version(Windows){ 287 const (char)[][1] libNames = ["$.dll"]; 288 } 289 else version(OSX){ 290 const(char)[][7] libNames = [ 291 "lib$.dylib", 292 "/usr/local/lib/lib$.dylib", 293 "/usr/local/lib/lib$/lib$.dylib", 294 "../Frameworks/$.framework/$", 295 "/Library/Frameworks/$.framework/$", 296 "/System/Library/Frameworks/$.framework/$", 297 "/opt/local/lib/lib$.dylib" 298 ]; 299 } 300 else version(Posix){ 301 const(char)[][8] libNames = [ 302 "$.so", 303 "/usr/local/lib/$.so", 304 "$.so.1", 305 "/usr/local/lib/$.so.1", 306 "lib$.so", 307 "/usr/local/lib/lib$.so", 308 "lib$.so.1", 309 "/usr/local/lib/lib$.so.1" 310 ]; 311 } 312 else static assert(0, "bindbc-$ is not yet supported on this platform."); 313 foreach(name; libNames) 314 { 315 lib = load(name.ptr); 316 if(lib != invalidHandle) 317 return _load(); 318 } 319 return false; 320 }`; 321 // };//Token strings for some reason seems to be jumping more lines than the expected 322 fileContent~=r" 323 private bool _load() 324 { 325 bool isOkay = true; 326 import std.stdio:writeln; 327 const size_t errs = errorCount(); 328 loadSymbols(); 329 if(errs != errorCount()) 330 { 331 isOkay = false; 332 import std.conv:to; 333 foreach(err; errors) 334 { 335 writeln(to!string(err.message)); 336 } 337 } 338 return isOkay; 339 }"; 340 // }; 341 fileContent = replaceAll(fileContent, DollarToLib, libName); 342 fileContent~="private void loadSymbols()\n{\n\t"; 343 fileContent~= generateBindSymbols(funcNames); 344 fileContent~="}"; 345 346 File _f = File("bindbc/"~libName~"/"~libName~"load.d", "w"); 347 _f.write(fileContent); 348 _f.close(); 349 } 350 351 void createPackage(string libName) 352 { 353 //No need to regenerate it everytime 354 if(exists("bindbc/"~libName~"/package.d")) 355 return; 356 string fileContent = q{ 357 module bindbc.$; 358 359 public import bindbc.$.funcs; 360 public import bindbc.$.$load; 361 public import bindbc.$.types; 362 }.replaceAll(DollarToLib, libName); 363 std.file.write("bindbc/"~libName~"/package.d", fileContent); 364 } 365 366 367 enum ERROR = -1; 368 369 string optFile; 370 string optDppArgs; 371 string optPresets; 372 bool optNoTypes; 373 bool optLoad; 374 string optCustom; 375 bool optFuncPrefix; 376 string[] optUsingPlugins = []; 377 bool optLoadAll; 378 string[][string] optPluginArgs; 379 bool optRecompile; 380 bool optDebug; 381 382 383 enum ReservedArgs : string 384 { 385 D_CONV = "d-conv" 386 } 387 /** 388 * Remove d-conv from the args and sets willConvertToD to true 389 */ 390 void getDConvPlugins() 391 { 392 import std.algorithm : countUntil; 393 foreach(pluginName, args; optPluginArgs) 394 { 395 long ind = countUntil(args, ReservedArgs.D_CONV); 396 if(ind != -1) 397 { 398 if(ind != args.length) 399 optPluginArgs[pluginName] = args[0..ind] ~ args[ind+1..$]; 400 else 401 optPluginArgs[pluginName] = args[0..ind]; 402 PluginAdapter.loadedPlugins[pluginName].willConvertToD = true; 403 } 404 } 405 } 406 407 void pluginArgsHandler(string opt, string value) 408 { 409 import std.array:split; 410 import std.algorithm:countUntil; 411 if(opt == "plugin-args|a") 412 { 413 if(value.countUntil("=") == -1) 414 return writeln("plugin-args wrong formatting!"); 415 string[] v = value.split("="); 416 string pluginName = v[0]; 417 if(v[1].countUntil("[") != -1 && v[1].countUntil("]") == v[1].length - 1) 418 optPluginArgs[pluginName]~= v[1][1..$-1].split(" "); 419 else 420 optPluginArgs[pluginName]~= v[1]; 421 } 422 } 423 424 void playPlugins(string cwd) 425 { 426 foreach(pluginName, pluginArgs; optPluginArgs) 427 { 428 Plugin p = PluginAdapter.loadedPlugins[pluginName]; 429 writeln("'", pluginName, "' under execution with arguments ", cwd~pluginArgs, "\n\n\n"); 430 int retVal = p.main(cwd ~ pluginArgs); 431 if(retVal == Plugin.SUCCESS) 432 { 433 string processed = p.convertToD_Pipe(); 434 if(p.willConvertToD) 435 processed= cppFuncsToD(processed, true); 436 if(p.onReturnControl(processed) == Plugin.ERROR) 437 goto PLUGIN_ERROR; 438 writeln("'", pluginName, "' finished tasks.\n\n\n"); 439 p.hasFinishedExecution = true; 440 } 441 else 442 { 443 PLUGIN_ERROR: writeln("Error ocurred while executing '", pluginName, "'!\n\t->", p.error); 444 } 445 } 446 } 447 448 void checkPresets(ref Regex!char targetReg) 449 { 450 if(optPresets != "") 451 { 452 switch(optPresets) 453 { 454 case "cimgui": 455 targetReg = Presets.cimguiFuncs; 456 optDppArgs = "--parse-as-cpp,--define CIMGUI_DEFINE_ENUMS_AND_STRUCTS"; 457 if(optFile == "") 458 optFile = "cimgui.h"; 459 break; 460 default: 461 writeln("Preset named '"~optPresets~"' does not exists"); 462 break; 463 } 464 } 465 } 466 467 void checkCustomRegex(ref Regex!char targetReg) 468 { 469 if(optPresets == "" && optCustom != "") 470 { 471 writeln(" 472 Please consider adding your custom function getter to the list. 473 Just create an issue or a pull request on https://www.github.com/MrcSnm/bindbc-generator 474 "); 475 string reg; 476 if(optFuncPrefix) 477 { 478 reg~=r"^(?:"; 479 reg~=optCustom; 480 reg~=r")(.+\);)$"; 481 } 482 else 483 reg~= optCustom; 484 writeln("Compiling regex"); 485 targetReg = regex(reg, "mg"); //Auto converts single slash to double for easier usage 486 writeln("Regex Generated:\n\t"~targetReg.toString); 487 } 488 } 489 490 void helpInfoSetup(ref GetoptResult helpInfo) 491 { 492 //Dpparg 493 helpInfo.options[0].help = "Arguments to be appended to dpp, --preprocess-only is always included. Pass multiple arguments via comma"; 494 //File 495 helpInfo.options[1].help = "Target header to get functions and types for generation"; 496 //Presets 497 helpInfo.options[2].help = r" 498 (Presets and custom are mutually exclusive) 499 Function getter presets: 500 cimgui - Preset used for compiling libcimgui -> https://github.com/cimgui/cimgui 501 "; 502 helpInfo.options[3].help = "Don't execute Dpp, and don't generate the types file"; 503 //Custom 504 helpInfo.options[4].help =r" 505 Flags m and g are always added, $1 must always match function without exports. 506 Examples: 507 void func(char* str); 508 int main(); 509 "; 510 //Prefix-only 511 helpInfo.options[5].help = r" 512 This will be the prefix of your regex. 513 The postfix will be a predefined one for function format: 514 Appends ^(?: at the start(The one which is meant to be ignored) 515 Appends )(.+\);)$ at the end (Finish the ignored one and append the function $1 one) 516 "; 517 //Plugin-load 518 helpInfo.options[6].help = r" 519 Loads plugins located at the plugins folder. For the plugin being loaded it must: 520 1: Export a function named export(Modulename) which returns a Plugin instance. 521 2: Have a compiled .dll or .so following the scheme 'libpluginPLUGIN_FOLDER_NAME' 522 2.1: If you need many exports in a single dll, create a package.d with public imports and 523 compile it, plugin finding is first folder only, i.e: not recursive. 524 "; 525 //Load all 526 helpInfo.options[7].help = r" 527 Loads every plugin located at the plugis folder"; 528 //Plugins args 529 helpInfo.options[8].help = r" 530 Plugins arguments to pass into the entrance point. 531 Only the plugins with at least args 1 arg will be executed, pass a null string if you wish 532 to pass only the current working dir. 533 534 Example on multiple args-> -a myplugin=[arg1 arg2 arg3] 535 536 Reserved arguments are: 537 d-conv -> Converts from C to D 538 "; 539 //Recompile 540 helpInfo.options[9].help = r" 541 Using this option will force a recompilation of the plugins!"; 542 //Debug 543 helpInfo.options[10].help = r" 544 Compile dynamic libraries with debug symbols enabled"; 545 } 546 547 bool checkHelpNeeded(ref GetoptResult helpInfo) 548 { 549 if(helpInfo.helpWanted || (optFile == "" && optUsingPlugins.length == 0)) 550 { 551 if(optFile == "" && optUsingPlugins.length == 0) 552 writeln("File options is missing, you should always specify the target or specify a plugin!"); 553 554 defaultGetoptPrinter(r" 555 Bindbc-generator options. 556 If you find an issue with the content generation, report it at 557 https://www.github.com/MrcSnm/bindbc-generator 558 ", 559 helpInfo.options); 560 return true; 561 } 562 return false; 563 } 564 565 bool checkPluginLoad() 566 { 567 if(optUsingPlugins.length != 0 || optLoadAll) 568 { 569 if(optLoadAll) 570 optUsingPlugins = PluginAdapter.loadPlugins(optUsingPlugins, optRecompile, optDebug); 571 else 572 PluginAdapter.loadPlugins(optUsingPlugins, optRecompile, optDebug); 573 int nullCount = 0; 574 if(optUsingPlugins.length == 0) 575 { 576 writeln("\n\nERROR!\nCould not load any plugin!"); 577 return false; 578 } 579 foreach(p; optPluginArgs) 580 { 581 if(p.length == 0) 582 nullCount++; 583 } 584 if(nullCount == optPluginArgs.length) 585 { 586 writeln(r" 587 Plugins loaded but none was specified for execution! 588 For executing it, you must at least specify one plugin arg. 589 Showing loaded plugins help info:"); 590 foreach(k, v; PluginAdapter.loadedPlugins) 591 { 592 if(v.getHelpInformation() == "") 593 writeln("\n\nWARNING!\n\nContact ", k, " provider! No help information is given"); 594 else 595 writeln("\n\n", k, "\n\n", 596 r"--------------------------------", 597 v.getHelpInformation()); 598 } 599 return false; 600 } 601 } 602 return true; 603 } 604 605 bool checkDppExecution() 606 { 607 string _f = optFile; 608 if(!optNoTypes) 609 { 610 if(optDppArgs == "") 611 { 612 writeln(r" 613 No dpp arg specified! 614 Beware that for this project uses dpp for generating struct and enums ONLY 615 Functions definitions comes from the .h file specified and then replaces with 616 D style 617 "); 618 } 619 File f = createDppFile(_f); 620 if(f.name == "") 621 return false; 622 executeDpp(f, optDppArgs); 623 } 624 return true; 625 } 626 627 bool checkPluginOnly() 628 { 629 return (optFile == "" && optPresets == "" && optCustom == "" && optDppArgs == "" && 630 (optUsingPlugins.length != 0 || optLoadAll)); 631 } 632 633 int main(string[] args) 634 { 635 GetoptResult helpInfo; 636 try 637 { 638 helpInfo = getopt( 639 args, 640 "dpparg|d", &optDppArgs, 641 "file|f", &optFile, 642 "presets|p", &optPresets, 643 "notypes|n", &optNoTypes, 644 "custom|c", &optCustom, 645 "use-func-prefix|u", &optFuncPrefix, 646 "load-plugins|l", &optUsingPlugins, 647 "load-all", &optLoadAll, 648 "plugin-args|a", &pluginArgsHandler, 649 "recompile|r", &optRecompile, 650 "debug", &optDebug 651 ); 652 } 653 catch(Exception e) 654 { 655 writeln(e.msg); 656 return Plugin.ERROR; 657 } 658 helpInfoSetup(helpInfo); 659 //I don't really understand what type is regex... 660 Regex!char targetReg; 661 662 if(!checkPluginLoad()) 663 return Plugin.ERROR; 664 getDConvPlugins(); 665 666 bool pluginOnly = checkPluginOnly(); 667 668 if(!pluginOnly) 669 { 670 checkPresets(targetReg); 671 checkCustomRegex(targetReg); 672 } 673 674 if(checkHelpNeeded(helpInfo)) 675 return 1; 676 677 if(!pluginOnly) 678 { 679 if(!checkDppExecution()) 680 return Plugin.ERROR; 681 if(optPresets == "" && optCustom == "") 682 { 683 writeln("ERROR:\nNo regex nor presets for getting functions specified\n"); 684 return Plugin.ERROR; 685 } 686 string funcs = getFuncs(optFile, targetReg); 687 if(funcs == "") 688 { 689 writeln("ERROR:\nNo hit was made by your function"); 690 return Plugin.ERROR; 691 } 692 string cleanFuncs = cleanPreFuncsDeclaration(funcs, targetReg); 693 string dfuncs = cppFuncsToD(cleanFuncs); 694 string[] darrFuncs = dfuncs.split("\n"); 695 696 //It will already remove darrFuncs params 697 string libName = stripExtension(optFile); 698 createFuncsFile(libName, darrFuncs); 699 createLibLoad(libName, darrFuncs); 700 createPackage(libName); 701 702 if(!optNoTypes) 703 remove(optFile.stripExtension ~ ".d"); 704 } 705 playPlugins(args[0]); 706 707 return Plugin.SUCCESS; 708 }