1 module gen_bindings;
2 
3 import std;
4 
5 alias writeText = std.file.write;
6 
7 void main(string[] args)
8 {
9     void writeUsage()
10     {
11         stdout.writefln("\nUsage: %s [options] <blend2d_repo_path>", args[0].baseName);
12     }
13 
14     if (args.length < 2)
15     {
16         writeUsage;
17         return;
18     }
19 
20     string dirBlend2d = args[1];
21     if (!dirBlend2d.exists || !dirBlend2d.isDir)
22     {
23         stderr.writeln("<blend2d_repo_path> is expected to be an existing directory: ", dirBlend2d);
24         writeUsage;
25         return;
26     }
27 
28     auto gen = new GenBindings(dirBlend2d);
29     gen.bindStatic;
30     gen.bindDynamic;
31     gen.save;
32 }
33 
34 class GenBindings
35 {
36     string dApi = "api.d";
37     string versionStatic = "version(BindBlend2D_Static) ";
38     string[] skipFnList = [
39         "blDefaultApproximationOptions",
40         "blFormatInfo",
41         "blMatrix2DMapPointDArrayFuncs",
42         "blNone",
43         "blPixelConverterConvert",
44         "blRuntimeAllocImpl",
45         "blRuntimeAllocAlignedImpl",
46         "blRuntimeFreeImpl"
47     ];
48 
49     string cwd;
50     string dirBlend2dRepoSrc;
51     string[string] dFiles;
52     string[] symbols;
53     string fileSymbols;
54     string fileBindStatic;
55     string fileBindDynamic;
56     Appender!(string[]) bufBindStatic;
57     Appender!(string[]) bufBindDynamic;
58     Appender!(string[]) bufBindSymbol;
59 
60     this(string dirBlend2d)
61     {
62         cwd = getcwd();
63         dirBlend2dRepoSrc = chainPath(dirBlend2d, "src/blend2d").array;
64         fileSymbols = chainPath(cwd, "symbols.txt").array;
65         string dirBindBC = chainPath(cwd, "../source/bindbc/blend2d").array;
66         fileBindStatic = chainPath(dirBindBC, "bindstatic.d").array;
67         fileBindDynamic = chainPath(dirBindBC, "binddynamic.d").array;
68 
69         bufBindStatic = appender!(string[]);
70         bufBindDynamic = appender!(string[]);
71         bufBindSymbol = appender!(string[]);
72 
73         initialize;
74     }
75 
76     void initialize()
77     {
78         dFiles = genDFiles;
79         symbols = readText(fileSymbols).splitLines;
80     }
81 
82     string[string] genDFiles()
83     {
84         chdir(dirBlend2dRepoSrc);
85         scope(exit) chdir(cwd);
86 
87         auto hFiles = dirEntries("", SpanMode.breadth).filter!(
88             f => f.name.endsWith(".h")
89                 && !f.name.endsWith("_p.h")
90                 && !f.name.endsWith("-impl.h")
91             ).array;
92 
93         typeof(return) result;
94 
95         foreach (i, ref h; parallel(hFiles, 1))
96         {
97             auto pid = spawnProcess(["dstep",
98                 "--collision-action=ignore",
99                 "--space-after-function-name=false",
100                 "--comments=false",
101                 h.name]);
102             enforce(pid.wait() == 0, "dstep failed on " ~ h.name);
103 
104             auto d = h.name.withExtension("d").array;
105             result[d] = readText(d);
106             remove(d);
107         }
108 
109         return result;
110     }
111 
112     void commentOutLines()
113     {
114         auto api = dFiles[dApi];
115         auto toBeCommentedOut = [
116             ctRegex!(r"^(struct .+;)", "m"),
117             ctRegex!(r"^(.* _Pragma\(.+;)", "m"),
118             ctRegex!(r"^(BLResult blResultFromWinError.+;)", "m"),
119             ctRegex!(r"^(BLResult blResultFromPosixError.+;)", "m"),
120         ];
121         foreach (ref re; toBeCommentedOut)
122             api = replaceAll(api, re, "//$&");
123         dFiles[dApi] = api;
124     }
125 
126     void addVersionStatic()
127     {
128         auto api = dFiles[dApi];
129         foreach (ref fn; symbols)
130             api = replaceAll(api, regex(r"^(.* " ~ fn ~ r"\()", "m"), versionStatic ~ "$&");
131         api ~= "\n" ~ versionStatic ~ "version(Windows) BLResult blResultFromWinError(uint e);";
132         api ~= "\n" ~ versionStatic ~ "version(Posix) BLResult blResultFromPosixError(int e);";
133         dFiles[dApi] = api;
134     }
135 
136     void bindStatic()
137     {
138         bufBindStatic.put(q"EOL
139 module bindbc.blend2d.bindstatic;
140 
141 version(BindBC_Static) version = BindBlend2D_Static;
142 
143 import core.stdc.stdint;
144 import core.stdc.stdarg;
145 
146 auto BL_MAJOR_VERSION(uint ver) { return ver >> 16; }
147 auto BL_MINOR_VERSION(uint ver) { return (ver >> 8) & ((1 << 8) - 1); }
148 auto BL_PATCH_VERSION(uint ver) { return ver & ((1 << 8) - 1); }
149 
150 enum BLEND2D_VERSION_MAJOR = BL_MAJOR_VERSION(BL_VERSION);
151 enum BLEND2D_VERSION_MINOR = BL_MINOR_VERSION(BL_VERSION);
152 enum BLEND2D_VERSION_REVISION = BL_PATCH_VERSION(BL_VERSION);
153 
154 private
155 enum expandEnum(EnumType, string fqnEnumType = EnumType.stringof) = (){
156     string expandEnum = "enum {";
157     foreach(m;__traits(allMembers, EnumType)) {
158         expandEnum ~= m ~ " = " ~ fqnEnumType ~ "." ~ m ~ ",";
159     }
160     expandEnum  ~= "}";
161     return expandEnum;
162 }();
163 
164 EOL");
165 
166         string[] enums;
167         auto re = ctRegex!(r"^enum ([^;{]+)$");
168         foreach (d; dFiles.keys.sort)
169             foreach (line; dFiles[d].splitLines)
170             {
171                 auto m = matchFirst(line, re);
172                 if (!m.empty)
173                     enums ~= "mixin(expandEnum!" ~ m[1] ~ ");";
174             }
175         enums ~= "";
176         bufBindStatic.put(enums.join("\n"));
177 
178         commentOutLines;
179         addVersionStatic;
180 
181         foreach (d; dFiles.keys.sort)
182         {
183             bufBindStatic.put("");
184             bufBindStatic.put("//****** " ~ d.baseName ~ " ******//");
185             bufBindStatic.put("");
186             bufBindStatic.put(dFiles[d].splitLines);
187         }
188     }
189 
190     void bindDynamic()
191     {
192         bufBindDynamic.put(q"EOL
193 module bindbc.blend2d.binddynamic;
194 
195 version(BindBlend2D_Static) {}
196 else version = BindBlend2D_Dynamic;
197 
198 version(BindBlend2D_Dynamic):
199 
200 import core.stdc.stdint;
201 import core.stdc.stdarg;
202 
203 import bindbc.loader;
204 import bindbc.blend2d.types;
205 import bindbc.blend2d.bindstatic;
206 
207 extern(C) @nogc nothrow alias pblDefaultApproximationOptions = const BLApproximationOptions;
208 __gshared pblDefaultApproximationOptions blDefaultApproximationOptions;
209 
210 extern(C) @nogc nothrow alias pblFormatInfo = const(BLFormatInfo)[BL_FORMAT_RESERVED_COUNT];
211 __gshared pblFormatInfo blFormatInfo;
212 
213 extern(C) @nogc nothrow alias pblMatrix2DMapPointDArrayFuncs = BLMapPointDArrayFunc[BL_MATRIX2D_TYPE_COUNT];
214 __gshared pblMatrix2DMapPointDArrayFuncs blMatrix2DMapPointDArrayFuncs;
215 
216 extern(C) @nogc nothrow alias pblNone = BLVariantCore[BL_IMPL_TYPE_COUNT];
217 __gshared pblNone blNone;
218 
219 extern(C) @nogc nothrow alias pblPixelConverterConvert = BLResult function(const(BLPixelConverterCore)* self, void* dstData, intptr_t dstStride, const(void)* srcData, intptr_t srcStride, uint w, uint h, const(BLPixelConverterOptions)* options);
220 __gshared pblPixelConverterConvert blPixelConverterConvert;
221 
222 version(Windows) {
223     extern(C) @nogc nothrow alias pblResultFromWinError = BLResult function(uint e);
224     __gshared pblResultFromWinError blResultFromWinError;
225 }
226 version(Posix) {
227     extern(C) @nogc nothrow alias pblResultFromPosixError = BLResult function(int e);
228     __gshared pblResultFromPosixError blResultFromPosixError;
229 }
230 
231 EOL");
232 
233         auto fns = symbols.filter!(a => skipFnList.find(a).empty);
234         auto re_fns = regex(" (" ~ fns.join("|") ~ r")\(");
235         foreach (line; bufBindStatic.data)
236         {
237             const m = line.matchFirst(re_fns);
238             if (m.empty)
239                 continue;
240 
241             string fn = m[1];
242             string fn_def = line.replaceFirst(fn, "function")[versionStatic.length .. $];
243             bufBindDynamic.put("extern(C) @nogc nothrow alias p" ~ fn ~ " = " ~ fn_def);
244             bufBindDynamic.put("__gshared p" ~ fn ~ " " ~ fn ~ ";");
245             bufBindDynamic.put("");
246             bufBindSymbol.put("    lib.bindSymbol_stdcall(" ~ fn ~ ", \"" ~ fn ~ "\");");
247         }
248 
249         bufBindDynamic.put(q"EOL
250 private {
251     SharedLib lib;
252     Blend2DSupport loadedVersion;
253 }
254 
255 @nogc nothrow:
256 
257 void unloadGLFW()
258 {
259     if(lib != invalidHandle) {
260         lib.unload();
261     }
262 }
263 
264 Blend2DSupport loadedBlend2DVersion() @safe
265 {
266     return loadedVersion;
267 }
268 
269 bool isBlend2DLoaded() @safe
270 {
271     return lib != invalidHandle;
272 }
273 
274 Blend2DSupport loadBlend2D()
275 {
276     version(Windows) {
277         const(char)[][1] libNames = ["blend2d.dll"];
278     }
279     else version(OSX) {
280         const(char)[][1] libNames = [
281             "blend2d.dylib"
282         ];
283     }
284     else version(Posix) {
285         const(char)[][1] libNames = [
286             "blend2d.so"
287         ];
288     }
289     else static assert(0, "bindbc-blend2d is not yet supported on this platform.");
290 
291     Blend2DSupport ret;
292     foreach(name; libNames) {
293         ret = loadBlend2D(name.ptr);
294         if(ret != Blend2DSupport.noLibrary) break;
295     }
296     return ret;
297 }
298 
299 Blend2DSupport loadBlend2D(const(char)* libName)
300 {
301     lib = load(libName);
302     if(lib == invalidHandle) {
303         return Blend2DSupport.noLibrary;
304     }
305 
306     auto errCount = errorCount();
307     loadedVersion = Blend2DSupport.badLibrary;
308 
309     lib.bindSymbol(cast(void**)&blDefaultApproximationOptions, "blDefaultApproximationOptions");
310     lib.bindSymbol(cast(void**)&blFormatInfo, "blFormatInfo");
311     lib.bindSymbol(cast(void**)&blMatrix2DMapPointDArrayFuncs, "blMatrix2DMapPointDArrayFuncs");
312     lib.bindSymbol(cast(void**)&blNone, "blNone");
313 
314     lib.bindSymbol_stdcall(blPixelConverterConvert, "blPixelConverterConvert");
315 
316     version(Windows) lib.bindSymbol_stdcall(blResultFromWinError, "blResultFromWinError");
317     version(Posix) lib.bindSymbol_stdcall(blResultFromPosixError, "blResultFromPosixError");
318 
319 EOL");
320 
321         bufBindDynamic.put(bufBindSymbol.data.join("\n"));
322 
323         bufBindDynamic.put(q"EOL
324 
325     if(errorCount() != errCount) return Blend2DSupport.badLibrary;
326     else loadedVersion = Blend2DSupport.bl00;
327 
328     return loadedVersion;
329 }
330 EOL");
331 
332     }
333 
334     void save()
335     {
336         writeText(fileBindStatic, bufBindStatic.data.join("\n"));
337         writeText(fileBindDynamic, bufBindDynamic.data.join("\n"));
338     }
339 }