From c399c53b88e2525fade9e0a155d998d7d37f96fa Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Tue, 15 Jan 2013 12:57:26 -0500 Subject: [PATCH 1/6] initial commit --- dtoh.d | 351 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 351 insertions(+) create mode 100644 dtoh.d diff --git a/dtoh.d b/dtoh.d new file mode 100644 index 0000000000..c37f61e982 --- /dev/null +++ b/dtoh.d @@ -0,0 +1,351 @@ +// FIXME: output unions too just like structs +// FIXME: check for compatible types + +import std.stdio; +import std.string : replace, toUpper, indexOf, strip; +import std.algorithm : map, startsWith; + +string getIfThere(Variant[string] obj, string key) { + auto ptr = key in obj; + return ptr ? ((*ptr).get!string) : null; +} + +void addLine(ref string s, string line) { + s ~= line ~ "\n"; +} + +int indexOfArguments(string type) { + int parenCount = 0; + foreach_reverse(i, c; type) { + if(c == ')') + parenCount++; + if(c == '(') + parenCount--; + if(parenCount == 0) + return i; + } + + assert(0); +} + +string getReturnType(string type, string[string] typeMapping) { + if(type.startsWith("extern")) { + type = type[type.indexOf(")") + 2 .. $]; // skip the ) and a space + } + + auto t = type[0 .. indexOfArguments(type)]; + if(t in typeMapping) + return typeMapping[t]; + return "void"; +} + +string getArguments(string type, string[string] typeMapping) { + auto argList = type[indexOfArguments(type) .. $][1 .. $-1]; // cutting the parens + + string newArgList; + + void handleArg(string arg) { + if(arg.length > 0 && arg[0] == ' ') + arg = arg[1 .. $]; + if(arg.length == 0) + return; + + if(newArgList.length) + newArgList ~= ", "; + + auto fullArg = arg; + string moreArg; + foreach(i, c; fullArg) { + if(c == '*' || c == '[' || c == '!') { + arg = fullArg[0 .. i]; + moreArg = fullArg[i .. $]; + break; + } + } + + auto cppArg = arg in typeMapping; + newArgList ~= cppArg ? *cppArg : "void /* "~arg~" */"; + newArgList ~= moreArg; // pointer, etc + } + + bool gotName; + int argStart; + int parensCount; + foreach(i, c; argList) { + if(c == '(' || c == '[') + parensCount++; + + if(parensCount) { + if(c == ')' || c == ']') { + parensCount--; + } + continue; + } + + if(c == ' ') { + handleArg(argList[argStart .. i]); + gotName = true; + argStart = i; + } + + if(c == ',') { + if(gotName) { + newArgList ~= argList[argStart .. i]; + } else { + handleArg(argList[argStart .. i]); + } + + gotName = false; + argStart = i + 1; + } + } + + if(gotName) { + newArgList ~= argList[argStart .. $]; + } else { + handleArg(argList[argStart .. $]); + } + + return "(" ~ newArgList ~ ")"; +} + +void main(string[] args) { + string jsonFilename; + bool useC; + foreach(arg; args[1 .. $]) { + if(arg == "-c") + useC = true; + else + jsonFilename = arg; + } + + string[string] typeMapping = [ + "int" : "long", // D int is fixed at 32 bit so I think this is more correct than using C int... + "uint" : "unsigned long", + "byte" : "char", + "ubyte" : "unsigned char", + "short" : "short", + "ushort" : "unsigned short", + "long" : "long long", + "ulong" : "unsigned long long", + + "float" : "float", + "double" : "double", + "real" : "long double", + + "char" : "char", + "void" : "void", + ]; + // pointers to any of these should work, as well as static arrays of them + // structs and interfaces are added below + // FIXME: function pointers composed of allowed types should be ok too + + import std.file; + auto moduleData = jsonToVariant(readText(jsonFilename)).get!(Variant[]); + foreach(mod; map!((a) => a.get!(Variant[string]))(moduleData)) { + auto filename = replace(mod["file"].get!string, ".d", ".h"); + auto guard = "D_" ~ toUpper(filename.replace(".", "_")); + string fileContents; + + fileContents.addLine("#ifndef " ~ guard); + fileContents.addLine("#define " ~ guard); + + fileContents ~= "\n"; + + foreach(member; map!((a) => a.get!(Variant[string]))(mod["members"].get!(Variant[]))) { + auto name = member.getIfThere("name"); + auto kind = member.getIfThere("kind"); + + if(kind == "struct") { + typeMapping[name] = name; + } else if(kind == "enum") { + // see the note on enum below, in the main switch + // we can't output the declarations very well, so we'll just map + // these to the base type so we at least have something + + auto base = member.getIfThere("base"); + if(base.length && base in typeMapping) { + typeMapping[name] = typeMapping[base]; + } + } else if(!useC && kind == "interface") { + typeMapping[name] = name ~ "*"; // D interfaces are represented as class pointers in C++ + } + } + + moduleMemberLoop: + foreach(member; map!((a) => a.get!(Variant[string]))(mod["members"].get!(Variant[]))) { + auto name = member.getIfThere("name"); + auto kind = member.getIfThere("kind"); + auto protection = member.getIfThere("protection"); + auto type = member.getIfThere("type"); + + if(protection == "private") + continue; + + switch(kind) { + case "function": + string line; + if(type.indexOf("extern (C++)") != -1) { + if(useC) + continue; + line ~= "extern \"C++\"\t"; + } else if(type.indexOf("extern (C)") != -1) { + if(useC) + line ~= "extern "; + else + line ~= "extern \"C\"\t"; + } else { + continue; + } + + auto returnType = getReturnType(type, typeMapping); + auto arguments = getArguments(type, typeMapping); + + line ~= returnType ~ " " ~ name ~ arguments ~ ";"; + + fileContents.addLine(line); + break; + case "variable": + // both manifest constants and module level variables show up here + // if it is extern(C) and __gshared, the global variable should be ok... + // but since the dmd json doesn't tell us that information, we have to assume + // it isn't accessible. + + // this space intentionally left blank until dmd is fixed + break; + case "enum": + // enums should be ok... but dmd's json doesn't tell us the value of the members, + // only the names + + // since the value is important for C++ to get it right, we can't use these either + + // this space intentionally left blank until dmd is fixed + break; + case "struct": + // plain structs are cool. We'll keep them with data members only, no functions. + // If it has destructors or postblits, that's no good, C++ won't know. So we'll + // output them only as opaque types.... if dmd only told us! + // FIXME: when dmd is fixed, check out the destructor dilemma + + string line; + + line ~= "\ntypedef struct " ~ name ~ " {"; + + foreach(method; map!((a) => a.get!(Variant[string]))(member["members"].get!(Variant[]))) { + auto memName = method.getIfThere("name"); + auto memType = method.getIfThere("type"); + + if(method.getIfThere("kind") != "variable") + continue; + + line ~= "\n\t"; + if(auto cType = (memType in typeMapping)) + line ~= *cType ~ " " ~ memName ~ ";"; + else assert(0, memType); + } + + line ~= "\n} " ~ name ~ ";\n"; + fileContents.addLine(line); + break; + case "interface": + if(useC) + continue; + // FIXME: the json doesn't seem to say if interfaces are extern C++ or not + + string line; + + line ~= "\nclass " ~ name ~ " {\n\tpublic:"; + + foreach(method; map!((a) => a.get!(Variant[string]))(member["members"].get!(Variant[]))) { + line ~= "\n\t\t"; + + auto funcName = method.getIfThere("name"); + auto funcType = method.getIfThere("type"); + + if(funcType.indexOf("extern (C++)") == -1) { + continue; + } + + auto returnType = getReturnType(funcType, typeMapping); + auto arguments = getArguments(funcType, typeMapping); + + line ~= "virtual " ~ returnType ~ " " ~ funcName ~ arguments ~ " = 0;"; + } + + line ~= "\n};\n"; + + fileContents.addLine(line); + break; + default: // do nothing + } + } + + fileContents.addLine("\n#endif"); + + if(exists(filename)) { + auto existingFile = readText(filename); + if(existingFile == fileContents) + continue; + } + + std.file.write(filename, fileContents); + } +} + + + + +import std.variant; +import std.json; + +Variant jsonToVariant(string json) { + auto decoded = parseJSON(json); + return jsonValueToVariant(decoded); +} + +Variant jsonValueToVariant(JSONValue v) { + Variant ret; + + final switch(v.type) { + case JSON_TYPE.STRING: + ret = v.str; + break; + case JSON_TYPE.UINTEGER: + ret = v.uinteger; + break; + case JSON_TYPE.INTEGER: + ret = v.integer; + break; + case JSON_TYPE.FLOAT: + ret = v.floating; + break; + case JSON_TYPE.OBJECT: + Variant[string] obj; + foreach(k, val; v.object) { + obj[k] = jsonValueToVariant(val); + } + + ret = obj; + break; + case JSON_TYPE.ARRAY: + Variant[] arr; + foreach(i; v.array) { + arr ~= jsonValueToVariant(i); + } + + ret = arr; + break; + case JSON_TYPE.TRUE: + ret = true; + break; + case JSON_TYPE.FALSE: + ret = false; + break; + case JSON_TYPE.NULL: + ret = null; + break; + } + + return ret; +} + From 1bc47cda12e9b110cfaace857bee9e1a1cbcc222 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Tue, 15 Jan 2013 13:06:11 -0500 Subject: [PATCH 2/6] a little documentation --- dtoh.d | 322 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 175 insertions(+), 147 deletions(-) diff --git a/dtoh.d b/dtoh.d index c37f61e982..0725fe7563 100644 --- a/dtoh.d +++ b/dtoh.d @@ -1,6 +1,21 @@ // FIXME: output unions too just like structs // FIXME: check for compatible types + +enum usage = +"Usage: dtoh [-c] [-h] file.json + +To generate a .json file, use dmd -X yourfile.d + +Options: + -c generate C instead of C++ + -h display this help + +The generated .h file can then be included in your +C or C++ project, giving easy access to extern(C) +and extern(C++) D functions and interfaces. +"; + import std.stdio; import std.string : replace, toUpper, indexOf, strip; import std.algorithm : map, startsWith; @@ -110,185 +125,198 @@ string getArguments(string type, string[string] typeMapping) { } void main(string[] args) { - string jsonFilename; - bool useC; - foreach(arg; args[1 .. $]) { - if(arg == "-c") - useC = true; - else - jsonFilename = arg; - } + try { + string jsonFilename; + bool useC; + foreach(arg; args[1 .. $]) { + if(arg == "-c") + useC = true; + else if(arg == "-h") { + writef("%s", usage); + return; + } else + jsonFilename = arg; + } + + if(jsonFilename.length == 0) { + writeln("No filename given, for help use dtoh -h"); + return; + } - string[string] typeMapping = [ - "int" : "long", // D int is fixed at 32 bit so I think this is more correct than using C int... - "uint" : "unsigned long", - "byte" : "char", - "ubyte" : "unsigned char", - "short" : "short", - "ushort" : "unsigned short", - "long" : "long long", - "ulong" : "unsigned long long", - - "float" : "float", - "double" : "double", - "real" : "long double", - - "char" : "char", - "void" : "void", - ]; - // pointers to any of these should work, as well as static arrays of them - // structs and interfaces are added below - // FIXME: function pointers composed of allowed types should be ok too - - import std.file; - auto moduleData = jsonToVariant(readText(jsonFilename)).get!(Variant[]); - foreach(mod; map!((a) => a.get!(Variant[string]))(moduleData)) { - auto filename = replace(mod["file"].get!string, ".d", ".h"); - auto guard = "D_" ~ toUpper(filename.replace(".", "_")); - string fileContents; - - fileContents.addLine("#ifndef " ~ guard); - fileContents.addLine("#define " ~ guard); - - fileContents ~= "\n"; - - foreach(member; map!((a) => a.get!(Variant[string]))(mod["members"].get!(Variant[]))) { - auto name = member.getIfThere("name"); - auto kind = member.getIfThere("kind"); - - if(kind == "struct") { - typeMapping[name] = name; - } else if(kind == "enum") { - // see the note on enum below, in the main switch - // we can't output the declarations very well, so we'll just map - // these to the base type so we at least have something - - auto base = member.getIfThere("base"); - if(base.length && base in typeMapping) { - typeMapping[name] = typeMapping[base]; + string[string] typeMapping = [ + "int" : "long", // D int is fixed at 32 bit so I think this is more correct than using C int... + "uint" : "unsigned long", + "byte" : "char", + "ubyte" : "unsigned char", + "short" : "short", + "ushort" : "unsigned short", + "long" : "long long", + "ulong" : "unsigned long long", + + "float" : "float", + "double" : "double", + "real" : "long double", + + "char" : "char", + "void" : "void", + ]; + // pointers to any of these should work, as well as static arrays of them + // structs and interfaces are added below + // FIXME: function pointers composed of allowed types should be ok too + + import std.file; + auto moduleData = jsonToVariant(readText(jsonFilename)).get!(Variant[]); + foreach(mod; map!((a) => a.get!(Variant[string]))(moduleData)) { + auto filename = replace(mod["file"].get!string, ".d", ".h"); + auto guard = "D_" ~ toUpper(filename.replace(".", "_")); + string fileContents; + + fileContents.addLine("#ifndef " ~ guard); + fileContents.addLine("#define " ~ guard); + + fileContents ~= "\n"; + + foreach(member; map!((a) => a.get!(Variant[string]))(mod["members"].get!(Variant[]))) { + auto name = member.getIfThere("name"); + auto kind = member.getIfThere("kind"); + + if(kind == "struct") { + typeMapping[name] = name; + } else if(kind == "enum") { + // see the note on enum below, in the main switch + // we can't output the declarations very well, so we'll just map + // these to the base type so we at least have something + + auto base = member.getIfThere("base"); + if(base.length && base in typeMapping) { + typeMapping[name] = typeMapping[base]; + } + } else if(!useC && kind == "interface") { + typeMapping[name] = name ~ "*"; // D interfaces are represented as class pointers in C++ } - } else if(!useC && kind == "interface") { - typeMapping[name] = name ~ "*"; // D interfaces are represented as class pointers in C++ } - } - moduleMemberLoop: - foreach(member; map!((a) => a.get!(Variant[string]))(mod["members"].get!(Variant[]))) { - auto name = member.getIfThere("name"); - auto kind = member.getIfThere("kind"); - auto protection = member.getIfThere("protection"); - auto type = member.getIfThere("type"); + moduleMemberLoop: + foreach(member; map!((a) => a.get!(Variant[string]))(mod["members"].get!(Variant[]))) { + auto name = member.getIfThere("name"); + auto kind = member.getIfThere("kind"); + auto protection = member.getIfThere("protection"); + auto type = member.getIfThere("type"); + + if(protection == "private") + continue; + + switch(kind) { + case "function": + string line; + if(type.indexOf("extern (C++)") != -1) { + if(useC) + continue; + line ~= "extern \"C++\"\t"; + } else if(type.indexOf("extern (C)") != -1) { + if(useC) + line ~= "extern "; + else + line ~= "extern \"C\"\t"; + } else { + continue; + } - if(protection == "private") - continue; + auto returnType = getReturnType(type, typeMapping); + auto arguments = getArguments(type, typeMapping); - switch(kind) { - case "function": - string line; - if(type.indexOf("extern (C++)") != -1) { - if(useC) - continue; - line ~= "extern \"C++\"\t"; - } else if(type.indexOf("extern (C)") != -1) { - if(useC) - line ~= "extern "; - else - line ~= "extern \"C\"\t"; - } else { - continue; - } + line ~= returnType ~ " " ~ name ~ arguments ~ ";"; - auto returnType = getReturnType(type, typeMapping); - auto arguments = getArguments(type, typeMapping); + fileContents.addLine(line); + break; + case "variable": + // both manifest constants and module level variables show up here + // if it is extern(C) and __gshared, the global variable should be ok... + // but since the dmd json doesn't tell us that information, we have to assume + // it isn't accessible. - line ~= returnType ~ " " ~ name ~ arguments ~ ";"; + // this space intentionally left blank until dmd is fixed + break; + case "enum": + // enums should be ok... but dmd's json doesn't tell us the value of the members, + // only the names - fileContents.addLine(line); - break; - case "variable": - // both manifest constants and module level variables show up here - // if it is extern(C) and __gshared, the global variable should be ok... - // but since the dmd json doesn't tell us that information, we have to assume - // it isn't accessible. + // since the value is important for C++ to get it right, we can't use these either - // this space intentionally left blank until dmd is fixed - break; - case "enum": - // enums should be ok... but dmd's json doesn't tell us the value of the members, - // only the names + // this space intentionally left blank until dmd is fixed + break; + case "struct": + // plain structs are cool. We'll keep them with data members only, no functions. + // If it has destructors or postblits, that's no good, C++ won't know. So we'll + // output them only as opaque types.... if dmd only told us! + // FIXME: when dmd is fixed, check out the destructor dilemma - // since the value is important for C++ to get it right, we can't use these either + string line; - // this space intentionally left blank until dmd is fixed - break; - case "struct": - // plain structs are cool. We'll keep them with data members only, no functions. - // If it has destructors or postblits, that's no good, C++ won't know. So we'll - // output them only as opaque types.... if dmd only told us! - // FIXME: when dmd is fixed, check out the destructor dilemma + line ~= "\ntypedef struct " ~ name ~ " {"; - string line; + foreach(method; map!((a) => a.get!(Variant[string]))(member["members"].get!(Variant[]))) { + auto memName = method.getIfThere("name"); + auto memType = method.getIfThere("type"); - line ~= "\ntypedef struct " ~ name ~ " {"; + if(method.getIfThere("kind") != "variable") + continue; - foreach(method; map!((a) => a.get!(Variant[string]))(member["members"].get!(Variant[]))) { - auto memName = method.getIfThere("name"); - auto memType = method.getIfThere("type"); + line ~= "\n\t"; + if(auto cType = (memType in typeMapping)) + line ~= *cType ~ " " ~ memName ~ ";"; + else assert(0, memType); + } - if(method.getIfThere("kind") != "variable") + line ~= "\n} " ~ name ~ ";\n"; + fileContents.addLine(line); + break; + case "interface": + if(useC) continue; + // FIXME: the json doesn't seem to say if interfaces are extern C++ or not - line ~= "\n\t"; - if(auto cType = (memType in typeMapping)) - line ~= *cType ~ " " ~ memName ~ ";"; - else assert(0, memType); - } + string line; - line ~= "\n} " ~ name ~ ";\n"; - fileContents.addLine(line); - break; - case "interface": - if(useC) - continue; - // FIXME: the json doesn't seem to say if interfaces are extern C++ or not + line ~= "\nclass " ~ name ~ " {\n\tpublic:"; - string line; + foreach(method; map!((a) => a.get!(Variant[string]))(member["members"].get!(Variant[]))) { + line ~= "\n\t\t"; - line ~= "\nclass " ~ name ~ " {\n\tpublic:"; + auto funcName = method.getIfThere("name"); + auto funcType = method.getIfThere("type"); - foreach(method; map!((a) => a.get!(Variant[string]))(member["members"].get!(Variant[]))) { - line ~= "\n\t\t"; + if(funcType.indexOf("extern (C++)") == -1) { + continue; + } - auto funcName = method.getIfThere("name"); - auto funcType = method.getIfThere("type"); + auto returnType = getReturnType(funcType, typeMapping); + auto arguments = getArguments(funcType, typeMapping); - if(funcType.indexOf("extern (C++)") == -1) { - continue; + line ~= "virtual " ~ returnType ~ " " ~ funcName ~ arguments ~ " = 0;"; } - auto returnType = getReturnType(funcType, typeMapping); - auto arguments = getArguments(funcType, typeMapping); + line ~= "\n};\n"; - line ~= "virtual " ~ returnType ~ " " ~ funcName ~ arguments ~ " = 0;"; - } + fileContents.addLine(line); + break; + default: // do nothing + } + } - line ~= "\n};\n"; + fileContents.addLine("\n#endif"); - fileContents.addLine(line); - break; - default: // do nothing + if(exists(filename)) { + auto existingFile = readText(filename); + if(existingFile == fileContents) + continue; } - } - fileContents.addLine("\n#endif"); - - if(exists(filename)) { - auto existingFile = readText(filename); - if(existingFile == fileContents) - continue; + std.file.write(filename, fileContents); } - - std.file.write(filename, fileContents); + } catch(Throwable t) { + writeln(t.toString()); + writef("%s", usage); } } From 7bc57807e036702692dfe5477a514f621c25b1ef Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Tue, 15 Jan 2013 16:23:08 -0500 Subject: [PATCH 3/6] i mixed up int and long --- dtoh.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dtoh.d b/dtoh.d index 0725fe7563..e63379ff97 100644 --- a/dtoh.d +++ b/dtoh.d @@ -144,8 +144,8 @@ void main(string[] args) { } string[string] typeMapping = [ - "int" : "long", // D int is fixed at 32 bit so I think this is more correct than using C int... - "uint" : "unsigned long", + "int" : "int", + "uint" : "unsigned int", "byte" : "char", "ubyte" : "unsigned char", "short" : "short", From 7d077b26d991dd5705e834900f66bea737a233b2 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Sat, 28 Dec 2013 16:31:48 -0500 Subject: [PATCH 4/6] updates since January --- dtoh.d | 549 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 293 insertions(+), 256 deletions(-) diff --git a/dtoh.d b/dtoh.d index e63379ff97..e98ce4ce26 100644 --- a/dtoh.d +++ b/dtoh.d @@ -1,21 +1,6 @@ // FIXME: output unions too just like structs // FIXME: check for compatible types - -enum usage = -"Usage: dtoh [-c] [-h] file.json - -To generate a .json file, use dmd -X yourfile.d - -Options: - -c generate C instead of C++ - -h display this help - -The generated .h file can then be included in your -C or C++ project, giving easy access to extern(C) -and extern(C++) D functions and interfaces. -"; - import std.stdio; import std.string : replace, toUpper, indexOf, strip; import std.algorithm : map, startsWith; @@ -29,300 +14,352 @@ void addLine(ref string s, string line) { s ~= line ~ "\n"; } -int indexOfArguments(string type) { - int parenCount = 0; - foreach_reverse(i, c; type) { - if(c == ')') - parenCount++; - if(c == '(') - parenCount--; - if(parenCount == 0) - return i; - } - - assert(0); -} +struct FunctionInfo { + string name; + string callingConvention; + string typeMangle; + string returnTypeMangle; -string getReturnType(string type, string[string] typeMapping) { - if(type.startsWith("extern")) { - type = type[type.indexOf(")") + 2 .. $]; // skip the ) and a space + struct Argument { + string name; + string typeMangle; + string[] storageClass; } - auto t = type[0 .. indexOfArguments(type)]; - if(t in typeMapping) - return typeMapping[t]; - return "void"; -} - -string getArguments(string type, string[string] typeMapping) { - auto argList = type[indexOfArguments(type) .. $][1 .. $-1]; // cutting the parens + Argument[] arguments; - string newArgList; + static FunctionInfo fromJsonInfo(Variant[string] obj) { + FunctionInfo info; - void handleArg(string arg) { - if(arg.length > 0 && arg[0] == ' ') - arg = arg[1 .. $]; - if(arg.length == 0) - return; + info.name = obj["name"].get!string; + info.typeMangle = obj["deco"].get!string; + info.returnTypeMangle = getReturnTypeMangle(info.typeMangle); + info.callingConvention = getCallingConvention(info.typeMangle); - if(newArgList.length) - newArgList ~= ", "; + if("parameters" in obj) + foreach(arg; map!(a => a.get!(Variant[string]))(obj["parameters"].get!(Variant[]))) { + Argument argInfo; + argInfo.name = getIfThere(arg, "name"); + argInfo.typeMangle = getIfThere(arg, "deco"); + if("storageClass" in arg) + foreach(sc; map!(a => a.get!string)(arg["storageClass"].get!(Variant[]))) + argInfo.storageClass ~= sc; - auto fullArg = arg; - string moreArg; - foreach(i, c; fullArg) { - if(c == '*' || c == '[' || c == '!') { - arg = fullArg[0 .. i]; - moreArg = fullArg[i .. $]; - break; - } + info.arguments ~= argInfo; } - auto cppArg = arg in typeMapping; - newArgList ~= cppArg ? *cppArg : "void /* "~arg~" */"; - newArgList ~= moreArg; // pointer, etc + return info; } +} - bool gotName; - int argStart; - int parensCount; - foreach(i, c; argList) { - if(c == '(' || c == '[') - parensCount++; - - if(parensCount) { - if(c == ')' || c == ']') { - parensCount--; - } - continue; - } +string getArguments(FunctionInfo info) { + string args = "("; + foreach(arg; info.arguments) { + if(args.length > 1) + args ~= ", "; + args ~= mangleToCType(arg.typeMangle); + // FIXME: check storage class + if(arg.name.length) + args ~= " " ~ arg.name; + } + args ~= ")"; + return args; +} - if(c == ' ') { - handleArg(argList[argStart .. i]); - gotName = true; - argStart = i; - } +string getReturnTypeMangle(string type) { + auto argEnd = type.indexOf("Z"); + if(argEnd == -1) + throw new Exception("Variadics not supported"); + return type[argEnd + 1 .. $]; +} - if(c == ',') { - if(gotName) { - newArgList ~= argList[argStart .. i]; - } else { - handleArg(argList[argStart .. i]); - } +string getCallingConvention(string type) { + switch(type[0]) { + case 'F': return "D"; + case 'U': return "C"; + case 'W': return "Windows"; + case 'V': return "Pascal"; + case 'R': return "C++"; + default: assert(0); + } +} - gotName = false; - argStart = i + 1; - } +string[] demangleName(string mangledName) { + string[] ret; + + import std.conv; + while(mangledName.length) { + size_t at = 0; + while(at < mangledName.length && mangledName[at] >= '0' && mangledName[at] <= '9') + at++; + auto length = to!int(mangledName[0 .. at]); + assert(length); + mangledName = mangledName[at .. $]; + ret ~= mangledName[0 .. length]; + mangledName = mangledName[length .. $]; } - if(gotName) { - newArgList ~= argList[argStart .. $]; - } else { - handleArg(argList[argStart .. $]); + return ret; +} + +string mangleToCType(string mangle) { + assert(mangle.length); + + string[string] basicTypeMapping = [ + "i" : "long", // D int is fixed at 32 bit so I think this is more correct than using C int... + "k" : "unsigned long", // D's uint + "g" : "char", // byte + "h" : "unsigned char", // ubyte + "s" : "short", + "t" : "unsigned short", + "l" : "long long", // D's long + "m" : "unsigned long long", // ulong + + "f" : "float", + "d" : "double", + "e" : "long double", // real + + "a" : "char", + "v" : "void", + ]; + + switch(mangle[0]) { + case 'O': // shared + throw new Exception("shared not supported"); + case 'H': // AA + throw new Exception("associative arrays not supported"); + case 'G': // static array + throw new Exception("static arrays not supported"); + case 'A': // array (slice) + throw new Exception("D arrays not supported, instead use a pointer+length pair"); + case 'x': // const + case 'y': // immutable + return "const " ~ mangleToCType(mangle[1 .. $]); + // 'Ng' == inout + case 'P': // pointer + return mangleToCType(mangle[1 .. $]) ~ "*"; + case 'C': // class or interface + return demangleName(mangle[1 .. $])[$-1] ~ "*"; + case 'S': // struct + return demangleName(mangle[1 .. $])[$-1]; + case 'E': // enum + return demangleName(mangle[1 .. $])[$-1]; + case 'D': // delegate + throw new Exception("Delegates are not supported"); + default: + if(auto t = mangle in basicTypeMapping) + return *t; + else + assert(0, mangle); } - return "(" ~ newArgList ~ ")"; + assert(0); } void main(string[] args) { - try { - string jsonFilename; - bool useC; - foreach(arg; args[1 .. $]) { - if(arg == "-c") - useC = true; - else if(arg == "-h") { - writef("%s", usage); - return; - } else - jsonFilename = arg; + string jsonFilename; + bool useC; + foreach(arg; args[1 .. $]) { + if(arg == "-c") + useC = true; + else + jsonFilename = arg; + } + // pointers to any of these should work, as well as static arrays of them + // structs and interfaces are added below + // FIXME: function pointers composed of allowed types should be ok too + + import std.file; + auto moduleData = jsonToVariant(readText(jsonFilename)).get!(Variant[]); + foreach(mod; map!((a) => a.get!(Variant[string]))(moduleData)) { + auto filename = replace(mod["file"].get!string, ".d", ".h"); + auto guard = "D_" ~ toUpper(filename.replace(".", "_")); + string fileContents; + + fileContents.addLine("#ifndef " ~ guard); + fileContents.addLine("#define " ~ guard); + fileContents.addLine("// generated from " ~ mod["file"].get!string); + + fileContents ~= "\n"; + + auto moduleName = getIfThere(mod, "name"); + if(moduleName.length == 0) + moduleName = filename[0 .. $-2]; + + // we're going to put all imports first, since C and C++ don't do forward references + // also going to forward declare the structs and classes as well. + // we might need a struct/class reference, even with just prototypes + foreach(member; map!((a) => a.get!(Variant[string]))(mod["members"].get!(Variant[]))) { + auto name = member.getIfThere("name"); + auto kind = member.getIfThere("kind"); + + switch(kind) { + case "import": + fileContents.addLine("#include \""~name~".h\""); + break; + case "struct": + fileContents.addLine("struct\t"~name~";"); + break; + case "interface": + fileContents.addLine("class\t"~name~";"); + break; + default: + // waiting for later + } } - if(jsonFilename.length == 0) { - writeln("No filename given, for help use dtoh -h"); - return; - } + // now, we'll do the rest of the members + moduleMemberLoop: + foreach(member; map!((a) => a.get!(Variant[string]))(mod["members"].get!(Variant[]))) { + auto name = member.getIfThere("name"); + auto kind = member.getIfThere("kind"); + auto protection = member.getIfThere("protection"); + auto type = member.getIfThere("deco"); - string[string] typeMapping = [ - "int" : "int", - "uint" : "unsigned int", - "byte" : "char", - "ubyte" : "unsigned char", - "short" : "short", - "ushort" : "unsigned short", - "long" : "long long", - "ulong" : "unsigned long long", - - "float" : "float", - "double" : "double", - "real" : "long double", - - "char" : "char", - "void" : "void", - ]; - // pointers to any of these should work, as well as static arrays of them - // structs and interfaces are added below - // FIXME: function pointers composed of allowed types should be ok too - - import std.file; - auto moduleData = jsonToVariant(readText(jsonFilename)).get!(Variant[]); - foreach(mod; map!((a) => a.get!(Variant[string]))(moduleData)) { - auto filename = replace(mod["file"].get!string, ".d", ".h"); - auto guard = "D_" ~ toUpper(filename.replace(".", "_")); - string fileContents; - - fileContents.addLine("#ifndef " ~ guard); - fileContents.addLine("#define " ~ guard); - - fileContents ~= "\n"; - - foreach(member; map!((a) => a.get!(Variant[string]))(mod["members"].get!(Variant[]))) { - auto name = member.getIfThere("name"); - auto kind = member.getIfThere("kind"); - - if(kind == "struct") { - typeMapping[name] = name; - } else if(kind == "enum") { - // see the note on enum below, in the main switch - // we can't output the declarations very well, so we'll just map - // these to the base type so we at least have something - - auto base = member.getIfThere("base"); - if(base.length && base in typeMapping) { - typeMapping[name] = typeMapping[base]; - } - } else if(!useC && kind == "interface") { - typeMapping[name] = name ~ "*"; // D interfaces are represented as class pointers in C++ - } - } + if(protection == "private") + continue; - moduleMemberLoop: - foreach(member; map!((a) => a.get!(Variant[string]))(mod["members"].get!(Variant[]))) { - auto name = member.getIfThere("name"); - auto kind = member.getIfThere("kind"); - auto protection = member.getIfThere("protection"); - auto type = member.getIfThere("type"); - - if(protection == "private") - continue; - - switch(kind) { - case "function": - string line; - if(type.indexOf("extern (C++)") != -1) { - if(useC) - continue; - line ~= "extern \"C++\"\t"; - } else if(type.indexOf("extern (C)") != -1) { - if(useC) - line ~= "extern "; - else - line ~= "extern \"C\"\t"; - } else { + switch(kind) { + case "function": + string line; + + auto info = FunctionInfo.fromJsonInfo(member); + if(info.callingConvention == "C++") { + if(useC) continue; - } + line ~= "extern \"C++\"\t"; + } else if(info.callingConvention == "C") { + if(useC) + line ~= "extern "; + else + line ~= "extern \"C\"\t"; + } else { + continue; + } - auto returnType = getReturnType(type, typeMapping); - auto arguments = getArguments(type, typeMapping); + line ~= mangleToCType(info.returnTypeMangle) + ~ " " ~ name ~ getArguments(info) ~ ";"; - line ~= returnType ~ " " ~ name ~ arguments ~ ";"; + fileContents.addLine(line); + break; + case "variable": + // both manifest constants and module level variables show up here + // if it is extern(C) and __gshared, the global variable should be ok... + // but since the dmd json doesn't tell us that information, we have to assume + // it isn't accessible. + + bool isEnum; + bool isGshared; + if("storageClass" in member) { + auto sc = member["storageClass"].get!(Variant[]); + foreach(c; sc) + if(c.get!string == "enum") { + isEnum = true; + break; + } else if(c.get!string == "__gshared") { + isGshared = true; + } + } + if(isEnum) { + auto line = "#define " ~ name ~ " "; + line ~= member.getIfThere("init"); fileContents.addLine(line); - break; - case "variable": - // both manifest constants and module level variables show up here - // if it is extern(C) and __gshared, the global variable should be ok... - // but since the dmd json doesn't tell us that information, we have to assume - // it isn't accessible. - - // this space intentionally left blank until dmd is fixed - break; - case "enum": - // enums should be ok... but dmd's json doesn't tell us the value of the members, - // only the names - - // since the value is important for C++ to get it right, we can't use these either - - // this space intentionally left blank until dmd is fixed - break; - case "struct": - // plain structs are cool. We'll keep them with data members only, no functions. - // If it has destructors or postblits, that's no good, C++ won't know. So we'll - // output them only as opaque types.... if dmd only told us! - // FIXME: when dmd is fixed, check out the destructor dilemma - - string line; - - line ~= "\ntypedef struct " ~ name ~ " {"; - - foreach(method; map!((a) => a.get!(Variant[string]))(member["members"].get!(Variant[]))) { - auto memName = method.getIfThere("name"); - auto memType = method.getIfThere("type"); - - if(method.getIfThere("kind") != "variable") - continue; + } else { + string line = "extern "; + if(!isGshared) + //line ~= "__declspec(thread) "; + continue moduleMemberLoop; // TLS not supported in this + fileContents.addLine(line ~ mangleToCType(type) ~ " " ~ name ~ ";"); + } - line ~= "\n\t"; - if(auto cType = (memType in typeMapping)) - line ~= *cType ~ " " ~ memName ~ ";"; - else assert(0, memType); - } + // this space intentionally left blank until dmd is fixed + break; + case "enum": + // enums should be ok... but dmd's json doesn't tell us the value of the members, + // only the names - line ~= "\n} " ~ name ~ ";\n"; - fileContents.addLine(line); - break; - case "interface": - if(useC) + // since the value is important for C++ to get it right, we can't use these either + + // this space intentionally left blank until dmd is fixed + break; + case "struct": + // plain structs are cool. We'll keep them with data members only, no functions. + // If it has destructors or postblits, that's no good, C++ won't know. So we'll + // output them only as opaque types.... if dmd only told us! + // FIXME: when dmd is fixed, check out the destructor dilemma + + string line; + + line ~= "\ntypedef struct " ~ name ~ " {"; + + foreach(method; map!((a) => a.get!(Variant[string]))(member["members"].get!(Variant[]))) { + auto memName = method.getIfThere("name"); + auto memType = method.getIfThere("deco"); + auto memKind = method.getIfThere("kind"); + + // if it has a dtor, we want this to be an opaque type only since + // otherwise it won't be used correctly in C++ + if(memKind == "destructor") + continue moduleMemberLoop; + + if(memKind != "variable") continue; - // FIXME: the json doesn't seem to say if interfaces are extern C++ or not - string line; + line ~= "\n\t"; + line ~= mangleToCType(memType) ~ " " ~ memName ~ ";"; + } - line ~= "\nclass " ~ name ~ " {\n\tpublic:"; + line ~= "\n} " ~ name ~ ";\n"; + fileContents.addLine(line); + break; + case "interface": + if(useC) + continue; + // FIXME: the json doesn't seem to say if interfaces are extern C++ or not - foreach(method; map!((a) => a.get!(Variant[string]))(member["members"].get!(Variant[]))) { - line ~= "\n\t\t"; + string line; - auto funcName = method.getIfThere("name"); - auto funcType = method.getIfThere("type"); + line ~= "\nclass " ~ name ~ " {\n\tpublic:"; - if(funcType.indexOf("extern (C++)") == -1) { - continue; - } + foreach(method; map!((a) => a.get!(Variant[string]))(member["members"].get!(Variant[]))) { + line ~= "\n\t\t"; - auto returnType = getReturnType(funcType, typeMapping); - auto arguments = getArguments(funcType, typeMapping); + auto info = FunctionInfo.fromJsonInfo(method); - line ~= "virtual " ~ returnType ~ " " ~ funcName ~ arguments ~ " = 0;"; + if(info.callingConvention != "C++") { + continue; } - line ~= "\n};\n"; + auto returnType = mangleToCType(info.returnTypeMangle); + auto arguments = getArguments(info); - fileContents.addLine(line); - break; - default: // do nothing - } - } + line ~= "virtual " ~ returnType ~ " " ~ info.name ~ arguments ~ " = 0;"; + } - fileContents.addLine("\n#endif"); + line ~= "\n};\n"; - if(exists(filename)) { - auto existingFile = readText(filename); - if(existingFile == fileContents) - continue; + fileContents.addLine(line); + break; + default: // do nothing } + } + + fileContents.addLine("\n#endif"); - std.file.write(filename, fileContents); + if(exists(filename)) { + auto existingFile = readText(filename); + if(existingFile == fileContents) + continue; } - } catch(Throwable t) { - writeln(t.toString()); - writef("%s", usage); + + std.file.write(filename, fileContents); } } - +// helpers tomake std.json easier to use import std.variant; import std.json; From 5f1f2ea0ec7282e18670afd23649f901c00db05d Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Mon, 10 Feb 2014 23:45:12 -0500 Subject: [PATCH 5/6] import fixes --- dtoh.d | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dtoh.d b/dtoh.d index e98ce4ce26..5eceea48ab 100644 --- a/dtoh.d +++ b/dtoh.d @@ -1,6 +1,9 @@ // FIXME: output unions too just like structs // FIXME: check for compatible types +// nice idea: convert modules to namespaces, but it won't work since then +// the mangles won't be right + import std.stdio; import std.string : replace, toUpper, indexOf, strip; import std.algorithm : map, startsWith; @@ -195,7 +198,8 @@ void main(string[] args) { switch(kind) { case "import": - fileContents.addLine("#include \""~name~".h\""); + if(!name.startsWith("std.") && !name.startsWith("core.")) + fileContents.addLine("#include \""~name~".h\""); break; case "struct": fileContents.addLine("struct\t"~name~";"); From a257eedb80fc7180ed0da108e54af334afc2c3ed Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Tue, 11 Feb 2014 10:40:09 -0500 Subject: [PATCH 6/6] long->int, default ctor, ref/out storage class --- dtoh.d | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/dtoh.d b/dtoh.d index 5eceea48ab..0af1a2e379 100644 --- a/dtoh.d +++ b/dtoh.d @@ -6,7 +6,7 @@ import std.stdio; import std.string : replace, toUpper, indexOf, strip; -import std.algorithm : map, startsWith; +import std.algorithm : map, startsWith, canFind; string getIfThere(Variant[string] obj, string key) { auto ptr = key in obj; @@ -61,7 +61,15 @@ string getArguments(FunctionInfo info) { if(args.length > 1) args ~= ", "; args ~= mangleToCType(arg.typeMangle); - // FIXME: check storage class + + // FIXME: check other storage classes + bool isRef = false; + foreach(sc; arg.storageClass) + if(sc == "ref" || sc == "out") + isRef = true; + + if(isRef) + args ~= "&"; if(arg.name.length) args ~= " " ~ arg.name; } @@ -109,8 +117,8 @@ string mangleToCType(string mangle) { assert(mangle.length); string[string] basicTypeMapping = [ - "i" : "long", // D int is fixed at 32 bit so I think this is more correct than using C int... - "k" : "unsigned long", // D's uint + "i" : "int", + "k" : "unsigned int", // D's uint "g" : "char", // byte "h" : "unsigned char", // ubyte "s" : "short", @@ -138,7 +146,12 @@ string mangleToCType(string mangle) { case 'x': // const case 'y': // immutable return "const " ~ mangleToCType(mangle[1 .. $]); - // 'Ng' == inout + case 'N': // "Ng" == inout + mangle = mangle[1 .. $]; + if(mangle.length && mangle[0] == 'g') { + goto case 'x'; // treat it as const + } + else goto _default; // case default; case 'P': // pointer return mangleToCType(mangle[1 .. $]) ~ "*"; case 'C': // class or interface @@ -150,6 +163,7 @@ string mangleToCType(string mangle) { case 'D': // delegate throw new Exception("Delegates are not supported"); default: + _default: if(auto t = mangle in basicTypeMapping) return *t; else @@ -183,6 +197,12 @@ void main(string[] args) { fileContents.addLine("#define " ~ guard); fileContents.addLine("// generated from " ~ mod["file"].get!string); + fileContents ~= "\n"; + if(useC) + fileContents.addLine("#include // for NAN"); + else + fileContents.addLine("#include // for NAN"); + fileContents ~= "\n"; auto moduleName = getIfThere(mod, "name"); @@ -297,6 +317,10 @@ void main(string[] args) { line ~= "\ntypedef struct " ~ name ~ " {"; + string defaultCtor = name~"::"~name~"() :"; + bool defaultCtorNeedsComma = false; + bool hasDefaultCtor = false; + foreach(method; map!((a) => a.get!(Variant[string]))(member["members"].get!(Variant[]))) { auto memName = method.getIfThere("name"); auto memType = method.getIfThere("deco"); @@ -310,12 +334,41 @@ void main(string[] args) { if(memKind != "variable") continue; + auto ct = mangleToCType(memType); + line ~= "\n\t"; - line ~= mangleToCType(memType) ~ " " ~ memName ~ ";"; + line ~= ct ~ " " ~ memName ~ ";"; + + string init; + if(ct.canFind("float") || ct.canFind("double")) { + init = useC ? "NAN" : "std::numeric_limits<"~ct~">::signaling_NaN()"; + } + + if(auto i = method.getIfThere("init")) + init = i; + + if(init.length) { + if(defaultCtorNeedsComma) + defaultCtor ~= ", "; + defaultCtor ~= "\n\t"~memName~"("~init~")"; + defaultCtorNeedsComma = true; + + hasDefaultCtor = true; + } + } + + if(hasDefaultCtor) { + defaultCtor ~= " {}"; + line ~= "\n\t" ~ name ~ "();"; } line ~= "\n} " ~ name ~ ";\n"; fileContents.addLine(line); + + if(hasDefaultCtor) { + fileContents.addLine(defaultCtor); + fileContents.addLine(""); + } break; case "interface": if(useC)