1 /**
2 Simple C++ header translation for us with the VST SDK.
3 Copyright: Guillaume Piolat 2018.
4 License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
5 */
6 module dplug.vst2.translatesdk;
7 
8 version(VST) // legacy version identifier
9 {
10 	static assert(false, "The \"VST\" version identifier should be replaced with \"VST2\" in your dub.json. See https://github.com/AuburnSounds/Dplug/wiki/Release-notes for upgrade instructions.");
11 }
12 
13 // Only does a semantic pass on this if the VST version identifier is defined.
14 // This allows building dplug:vst even without a VST2 SDK (though nothing will be defined in this case)
15 version(VST2): 
16 
17 import std.string;
18 
19 mixin(translateCppHeaderToD(cast(string) import("aeffect.h") ~ import("aeffectx.h")));
20 
21 string translateCppHeaderToD(string source) @safe
22 {
23     int index = 0;
24 
25     string nextLine()
26     {
27         if (index >= source.length)
28             return null;
29 
30         int lastIndex = index;
31         while(source[index] != '\n')
32             index++;
33         index++;
34         int offset = 1;
35         if (index - 2 >= 0 && source[index - 2] == '\r')
36             offset = 2;
37         return source[lastIndex..index-offset];
38     }
39 
40     static int indexOfChar(string source, char needle) pure @safe
41     {
42         int max = cast(int)(source.length);
43         for(int i = 0; i < max; ++i)
44         {
45             if (source[i] == needle)
46                 return i;
47         }
48         return -1;
49     }
50 
51     static int indexOf(string source, string needle) pure @safe
52     {
53         int max = cast(int)(source.length) - cast(int)needle.length;
54         for(int i = 0; i < max; ++i)
55         {
56             if (source[i] == needle[0])
57             {
58                 for (int j = 1; j < needle.length; ++j)
59                 {
60                     if (source[i+j] != needle[j])
61                         goto nope;
62                 }
63                 return i; // found
64             }
65             nope:
66         }
67         return -1;
68     }
69 
70     static bool startsWith(string source, string prefix) pure @safe
71     {
72         if (source.length < prefix.length)
73             return false;
74         return source[0..prefix.length] == prefix;
75     }
76 
77     static string replace(string source, string replaceThis, string byThis) pure @safe
78     {
79         int ind = indexOf(source, replaceThis);
80         if (ind != -1)
81             return source[0..ind] ~ byThis ~ source[ind+replaceThis.length..$];
82         else
83             return source;
84     }
85 
86     string result;
87 
88     bool inMultilineComment = false;
89     
90     // state machine, very simple
91     bool inEnum = false;
92     bool inStruct = false;
93     bool inMethod = false;
94 
95     bool inSharpIf = false;
96     bool inSharpElseOrElif = false;
97 
98     result ~= "alias short VstInt16;\n";
99     result ~= "alias int VstInt32;\n";
100     result ~= "alias long VstInt64;\n";
101     result ~= "alias ptrdiff_t VstIntPtr;\n";
102 
103     for(string line = nextLine(); line !is null; line = nextLine())
104     {
105 
106   
107         // strip spaces and TABs
108         while (line.length > 1 && (line[0] == ' ' || line[0] == '\t'))
109         {
110             line = line[1..$];
111         }
112 
113         // remove single-line comments
114         int posSingleLineComment = indexOf(line, "//");
115         if (posSingleLineComment != -1)
116         {
117             line = line[0..posSingleLineComment];            
118         }
119 
120         // strip line-ending spaces and TABS
121         while (line.length >= 1 && (line[$-1] == ' ' || line[$-1] == '\t'))
122         {
123             line = line[0..$-1];
124         }
125 
126         if (line.length == 0)
127             continue;
128 
129 
130         // passed comments, preprocessor emulation
131         // something that seems to work with the VST2 SDK: go into every #if, but never in #else or #elif
132         if (line[0] == '#')
133         {
134             if (startsWith(line, "#pragma"))
135             {
136                 continue;
137             }
138             else if (startsWith(line, "#if"))
139             {
140                 inSharpIf = true;
141                 continue;
142             }
143             else if (startsWith(line, "#define"))
144             {
145                 continue;
146             }
147             else if (startsWith(line, "#elif") || startsWith(line, "#else"))
148             {
149                 inSharpIf = false;
150                 inSharpElseOrElif = true;
151                 continue;
152             }
153             else if (startsWith(line, "#endif"))
154             {
155                 inSharpIf = false;
156                 inSharpElseOrElif = false;
157                 continue;
158             }            
159         }
160         if (inSharpElseOrElif)
161         {
162             continue; // ignore lines in #else or #elif
163         }
164 
165         // replace the DECLARE_VST_DEPRECATED preprocessor macro
166         int iDep = indexOf(line, "DECLARE_VST_DEPRECATED");
167         while(iDep != -1)
168         {            
169             int iFirstOpenParen = indexOfChar(line[iDep..$], '(') + iDep;
170             int iFirstClosParen = indexOfChar(line[iDep..$], ')') + iDep;
171          
172             if (iFirstOpenParen == -1 || iFirstClosParen == -1)
173                 break;
174  
175             line = line[0..iDep] 
176                     ~ "DEPRECATED_" 
177                     ~ line[iFirstOpenParen+1..iFirstClosParen] 
178                     ~ line[iFirstClosParen+1..$];
179             iDep = indexOf(line, "DECLARE_VST_DEPRECATED");
180         }
181 
182         // passed preprocessor and comments
183 
184         if (startsWith(line, "typedef"))
185         {
186             if (startsWith(line, "typedef struct"))
187                 line = line[8..$];
188             else
189                 continue;
190         }
191         if (!inStruct && startsWith(line, "struct "))
192         {
193             bool isForward = (line[$-1] == ';');
194             if (isForward)
195                 continue;
196 
197             inStruct = true;
198             string structName = line[7..$];
199             if (structName == "AEffect") // hackish but else it's a macro job
200                 line = "align(8) " ~ line;
201             result ~= line ~ "\n";
202             continue;
203         }
204         else if (!inEnum && startsWith(line, "enum "))
205         {
206             inEnum = true;
207             string enumName = line[5..$];
208             result ~= "alias " ~ enumName ~ " = int;\n";
209             result ~= "enum : " ~ enumName ~ "\n";
210             continue;
211         }
212         else if (startsWith(line, "{"))
213         {
214             if (inEnum || (inStruct && !inMethod))
215                 result ~= "{\n";
216             continue;
217         }
218         else if (startsWith(line, "}"))
219         {
220             if (inStruct)
221             {
222                 if (inMethod)
223                     inMethod = false;
224                 else
225                 {
226                     inStruct = false;
227                     result ~= "}\n\n";
228                 }
229             }
230 
231             if (inEnum)
232             {
233                 inEnum = false;
234                 result ~= "}\n\n";
235             }
236             continue;
237         }
238         else if (inEnum)
239         {
240             result ~= "   " ~ line ~ "\n";
241         }
242         else if (inStruct)
243         {
244             // is this a method? the line would contain parens
245             if (indexOfChar(line, '(') != -1 
246                 || indexOfChar(line, ')') != -1)
247             {
248                 inMethod = true;
249                 continue;
250             }
251 
252             // fix up keyword-named fields
253             if (line[$-8..$] == "version;")
254                 line = line[0..$-8] ~ "version_;";
255 
256             // fix up array declarations
257             int indOpen = indexOfChar(line, '[');
258             int indClose = indexOfChar(line, ']');
259             int indSpace = indexOfChar(line, ' ');
260             // position of the first space is where we'll move the indexing
261             if (indOpen != -1 && indClose != -1 && indSpace != -1
262                 && indSpace < indOpen && indClose > indOpen)
263             {
264                 line = line[0..indSpace] 
265                       ~ line[indOpen..indClose+1] 
266                       ~ line[indSpace..indOpen]
267                       ~ line[indClose+1..$];
268             }
269             // replace some unknown types
270             line = replace(line, "unsigned char", "char");
271 
272             if (!inMethod)
273                 result ~= "   " ~ line ~ "\n";
274         }
275     }
276 
277     // Parsing C++ function declaration is harder than the rest of the header, deferred
278     result ~= "alias extern(C) nothrow VstIntPtr function(AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt) AEffectDispatcherProc;\n";
279     result ~= "alias extern(C) nothrow @nogc VstIntPtr function(AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt) HostCallbackFunction;\n";
280     result ~= "alias extern(C) nothrow void function(AEffect* effect, float** inputs, float** outputs, VstInt32 sampleFrames) AEffectProcessProc;\n";
281     result ~= "alias extern(C) nothrow void function(AEffect* effect, double** inputs, double** outputs, VstInt32 sampleFrames) AEffectProcessDoubleProc;\n";
282     result ~= "alias extern(C) nothrow void function(AEffect* effect, VstInt32 index, float parameter) AEffectSetParameterProc;\n";
283     result ~= "alias extern(C) nothrow float function(AEffect* effect, VstInt32 index) AEffectGetParameterProc;";
284     return result;
285 }
286 
287 unittest
288 {
289     // For debugging purpose, uses the same parser but at runtime.
290     auto S = translateCppHeaderToD(cast(string)( import("aeffect.h") ~ import("aeffectx.h")));
291 }