1 /+
2 +            Copyright 2022 – 2023 Aya Partridge
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 bindbc.sdl.codegen;
8 
9 import bindbc.sdl.config: staticBinding;
10 
11 /*regex: function decl => makeFnBinds decl
12 ^[ \t]*([A-Za-z0-9_()*\[\]]+) (\w+) ?\(([A-Za-z0-9_()*, .=\[\]]*)\);
13 \t\t[q{$1}, q{$2}, q{$3}],
14 */
15 enum makeFnBinds = (string[3][] fns) nothrow pure @safe{
16 	string makeFnBinds = ``;
17 	string[] symbols;
18 	static if(staticBinding){
19 		foreach(fn; fns){
20 			makeFnBinds ~= "\n\t"~fn[0]~` `~fn[1]~`(`~fn[2]~`);`;
21 		}
22 	}else{
23 		foreach(fn; fns){
24 			if(fn[2].length > 3 && fn[2][$-3..$] == "..."){
25 				//version(WebAssembly){
26 					makeFnBinds ~= "\n\t private "~fn[0]~` function(`~fn[2]~`) _`~fn[1]~`;`;
27 					makeFnBinds ~= "\n\t alias "~fn[1]~` = _`~fn[1]~`;`;
28 				/+}else{
29 					size_t lastCommaPos = size_t.max; //a guaranteed fail if it's never found
30 					string params = "";
31 					bool capture = false;
32 					bool firstArg = true;
33 					foreach_reverse(i, c; fn[2]){
34 						if(capture){
35 							if(c == ' '){
36 								capture = false;
37 							}else{
38 								params = c ~ params;
39 							}
40 						}else if(c == ','){
41 							capture = true;
42 							if(!firstArg){
43 								params = ", " ~ params;
44 							}else{
45 								lastCommaPos = i;
46 								firstArg = false;
47 							}
48 						}
49 					}
50 					string namedParams = fn[2][0..lastCommaPos];
51 					makeFnBinds ~= "\n\tprivate "~fn[0]~` function(`~namedParams~`, void* argPtr) _`~fn[1]~`;`;
52 					if(fn[0] == "void"){
53 						makeFnBinds ~= "\n\textern(D) "~fn[0]~` `~fn[1]~`(`~fn[2]~`){ _`~fn[1]~`(`~params~`, _argptr); }`;
54 					}else{
55 						makeFnBinds ~= "\n\textern(D) "~fn[0]~` `~fn[1]~`(`~fn[2]~`){ return _`~fn[1]~`(`~params~`, _argptr); }`;
56 					}
57 				}+/
58 			}else{
59 				makeFnBinds ~= "\n\tprivate "~fn[0]~` function(`~fn[2]~`) _`~fn[1]~`;`;
60 				if(fn[0] == "void"){
61 					makeFnBinds ~= "\n\t"~fn[0]~` `~fn[1]~`(`~fn[2]~`){ _`~fn[1]~`(__traits(parameters)); }`;
62 				}else{
63 					makeFnBinds ~= "\n\t"~fn[0]~` `~fn[1]~`(`~fn[2]~`){ return _`~fn[1]~`(__traits(parameters)); }`;
64 				}
65 			}
66 			symbols ~= fn[1];
67 		}
68 	}
69 	return [makeFnBinds] ~ symbols;
70 };
71 
72 enum joinFnBinds = (string[][] list) nothrow pure @safe{
73 	string joined = `extern(C) @nogc nothrow`;
74 	string[] symbols;
75 	
76 	static if(staticBinding){
77 		joined ~= `{`;
78 		foreach(item; list){
79 			joined ~= item[0];
80 		}
81 	}else{
82 		joined ~= ` __gshared{`;
83 		foreach(item; list){
84 			joined ~= item[0];
85 			symbols ~= item[1..$];
86 		}
87 	}
88 	joined ~= "\n}";
89 	
90 	static if(!staticBinding){
91 		joined ~= "\n\nimport bindbc.loader: SharedLib, bindSymbol;\nvoid bindModuleSymbols(SharedLib lib) @nogc nothrow{";
92 		foreach(symbol; symbols){
93 			joined ~= "\n\tlib.bindSymbol(cast(void**)&_"~symbol~`, "`~symbol~`");`;
94 			//joined ~= "\n\tassert("~symbol~` != null);`;
95 		}
96 		joined ~= "\n}";
97 	}
98 	
99 	return joined;
100 };
101 
102 enum makeDynloadFns = (string name, string[] bindModules) nothrow pure @safe{
103 	string dynloadFns = `
104 void unloadSDL`~name~`(){ if(lib != invalidHandle) lib.unload(); }
105 
106 bool isSDL`~name~`Loaded(){ return lib != invalidHandle; }
107 
108 SDL`~name~`Support loadSDL`~name~`(){
109 	const(char)[][libNamesCT.length] libNames = libNamesCT;
110 	
111 	SDL`~name~`Support ret;
112 	foreach(name; libNames){
113 		ret = loadSDL`~name~`(name.ptr);
114 		//TODO: keep trying until we get the version we want, otherwise default to the highest one?
115 		if(ret != SDL`~name~`Support.noLibrary && ret != SDL`~name~`Support.badLibrary) break;
116 	}
117 	return ret;
118 }
119 
120 SDL`~name~`Support loadSDL`~name~`(const(char)* libName){
121 	lib = bindbc.loader.load(libName);
122 	if(lib == invalidHandle){
123 		return SDL`~name~`Support.noLibrary;
124 	}
125 	
126 	auto errCount = errorCount();
127 	loadedVersion = SDL`~name~`Support.badLibrary;
128 `;
129 	
130 	foreach(mod; bindModules){
131 		dynloadFns ~= "\n\t"~mod~".bindModuleSymbols(lib);";
132 	}
133 	
134 	dynloadFns ~= `
135 	
136 	if(errCount == errorCount()) loadedVersion = sdl`~name~`Support; //this is a white-lie in order to maintain backwards-compatibility :(
137 	return loadedVersion;
138 }`;
139 	
140 	return dynloadFns;
141 };