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 @nogc 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 @nogc void function(AEffect* effect, float** inputs, float** outputs, VstInt32 sampleFrames) AEffectProcessProc;\n"; 281 result ~= "alias extern(C) nothrow @nogc void function(AEffect* effect, double** inputs, double** outputs, VstInt32 sampleFrames) AEffectProcessDoubleProc;\n"; 282 result ~= "alias extern(C) nothrow @nogc void function(AEffect* effect, VstInt32 index, float parameter) AEffectSetParameterProc;\n"; 283 result ~= "alias extern(C) nothrow @nogc 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 }