├── .gitattributes ├── .gitignore ├── README.md └── src ├── .vscode ├── launch.json └── tasks.json ├── Config.cs ├── CppPrimitiveTypeExt.cs ├── CppSourceSpanExt.cs ├── CppTypedefExt.cs ├── Debug.cs ├── Program.cs ├── TypedefMap.cs ├── VGenerator.csproj └── VGenerator ├── ParsedFile.cs ├── V.cs └── VGenerator.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | binaries/VGenerator.exe filter=lfs diff=lfs merge=lfs -text 2 | binaries/VGenerator_linux filter=lfs diff=lfs merge=lfs -text 3 | binaries/VGenerator filter=lfs diff=lfs merge=lfs -text 4 | binaries/* filter=lfs diff=lfs merge=lfs -text 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # V-C-Wrapper-Generator 2 | Helper for generating Odin or V wrapper code from C source. Use the "odin" branch to get to the Odin generator. 3 | 4 | 5 | ## Requirements for Building 6 | - dotnet Core 3 7 | 8 | 9 | ## Building 10 | - open the project in VS Code and use the pre-configured build task (Ctrl/Cmd + Shift + b) 11 | 12 | 13 | ## Usage 14 | - **help**: run `./VGenerator -h` to see help on the command line 15 | - **config**: run `./VGenerator -c filename.json` to create a template file which will dictate how V generation is done. There are several example configs in the `Program.cs` file as well. 16 | - **generate**: run `./VGenerator -g filename.json` to generate the V wrapper 17 | 18 | 19 | ## Generating Binaries 20 | Binaries can be generated with the following commands: 21 | ``` 22 | dotnet publish -c Release -r osx.10.12-x64 /p:PublishSingleFile=true /p:PublishTrimmed=true 23 | dotnet publish -c Release -r win10-x64 /p:PublishSingleFile=true /p:PublishTrimmed=true 24 | dotnet publish -c Release -r linux-x64 /p:PublishSingleFile=true /p:PublishTrimmed=true 25 | ``` 26 | -------------------------------------------------------------------------------- /src/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp3.0/VGenerator.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach", 24 | "processId": "${command:pickProcess}" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /src/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "command": "dotnet", 9 | "type": "shell", 10 | "args": [ 11 | "build", 12 | // Ask dotnet build to generate full paths for file names. 13 | "/property:GenerateFullPaths=true", 14 | // Do not generate summary otherwise it leads to duplicate errors in Problems panel 15 | "/consoleloggerparameters:NoSummary" 16 | ], 17 | "group": "build", 18 | "presentation": { 19 | "reveal": "silent" 20 | }, 21 | "problemMatcher": "$msCompile" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /src/Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using CppAst; 6 | 7 | namespace Generator 8 | { 9 | public class Config 10 | { 11 | public string SrcDir {get; set;} 12 | public string DstDir {get; set;} 13 | public string ModuleName {get; set;} 14 | public string VWrapperFileName {get; set;} 15 | public string CDeclarationFileName {get; set;} 16 | public bool SingleVFileExport {get; set;} = false; 17 | public bool CopyHeadersToDstDir {get; set;} = false; 18 | 19 | /// 20 | /// if a function name contains any of the strings present here it will be excluded from 21 | /// code generation. 22 | /// 23 | public string[] ExcludeFunctionsThatContain {get; set;} = new string[] {}; 24 | 25 | /// 26 | /// if a function name starts with any prefix present, it will be stripped before writing the 27 | /// V function. Note that this is the C function prefix. 28 | /// 29 | public string[] StripPrefixFromFunctionNames {get; set;} = new string[] {}; 30 | 31 | /// 32 | /// if true, if there is a common prefix for all the enum values it will be stripped when 33 | /// generating the V items 34 | /// 35 | public bool StripEnumItemCommonPrefix {get; set;} = true; 36 | 37 | /// 38 | /// used when UseHeaderFolder is true to determine if a file should be placed in the module 39 | /// root folder. If the folder the file is in is BaseSourceFolder it will be placed in the 40 | /// root folder. 41 | /// 42 | public string BaseSourceFolder {get; set;} 43 | 44 | /// 45 | /// if true, the folder the header is in will be used for the generated V file 46 | /// 47 | public bool UseHeaderFolder {get; set;} = true; 48 | 49 | /// 50 | /// custom map of C types to V types. Most default C types will be handled automatically. 51 | /// 52 | public Dictionary CTypeToVType {get; set;} = new Dictionary(); 53 | 54 | /// 55 | /// All the header files that should be parsed and converted. 56 | /// 57 | public string[] Files {get; set;} 58 | 59 | /// 60 | /// All the header files that should be excluded from conversion. If a file should be declared in the c declaration 61 | /// but not wrapped it should be added to ExcludedFromVWrapperFiles 62 | /// 63 | public string[] ExcludedFiles {get; set;} 64 | 65 | /// 66 | /// All the header files that should be excluded from the V wrapper 67 | /// 68 | public string[] ExcludedFromVWrapperFiles {get; set;} 69 | 70 | /// 71 | /// List of the defines. 72 | /// 73 | public string[] Defines {get; set;} = new string[] {}; 74 | 75 | /// 76 | /// List of the include folders. 77 | /// 78 | public string[] IncludeFolders {get; set;} = new string[] {}; 79 | 80 | /// 81 | /// List of the system include folders. 82 | /// 83 | public string[] SystemIncludeFolders {get; set;} = new string[] {}; 84 | 85 | /// 86 | /// List of the additional arguments passed directly to the C++ Clang compiler. 87 | /// 88 | public string[] AdditionalArguments {get; set;} = new string[] {}; 89 | 90 | /// 91 | /// Gets or sets a boolean indicating whether un-named enum/struct referenced by a typedef will be renamed directly to the typedef name. Default is true 92 | /// 93 | public bool AutoSquashTypedef {get; set;} = true; 94 | 95 | /// 96 | /// Controls whether the codebase should be parsed as C or C++ 97 | /// 98 | public bool ParseAsCpp {get; set;} = true; 99 | 100 | public bool ParseComments {get; set;} = false; 101 | 102 | /// 103 | /// System Clang target. Default is "darwin" 104 | /// 105 | public string TargetSystem {get; set;} = "darwin"; 106 | 107 | public CppParserOptions ToParserOptions() 108 | { 109 | Validate(); 110 | AddSystemIncludes(); 111 | 112 | var opts = new CppParserOptions(); 113 | opts.Defines.AddRange(Defines); 114 | opts.IncludeFolders.AddRange(ToAbsolutePaths(IncludeFolders)); 115 | opts.SystemIncludeFolders.AddRange(SystemIncludeFolders); 116 | opts.AdditionalArguments.AddRange(AdditionalArguments); 117 | opts.AutoSquashTypedef = AutoSquashTypedef; 118 | opts.TargetSystem = TargetSystem; 119 | opts.ParseComments = ParseComments; 120 | 121 | return opts; 122 | } 123 | 124 | void Validate() 125 | { 126 | // resolve paths 127 | var homeFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal); 128 | if (!Path.IsPathRooted(DstDir)) 129 | DstDir = DstDir.Replace("~", homeFolder); 130 | 131 | if (string.IsNullOrEmpty(VWrapperFileName)) 132 | throw new ArgumentException(nameof(VWrapperFileName)); 133 | if (string.IsNullOrEmpty(ModuleName)) 134 | throw new ArgumentException(nameof(ModuleName)); 135 | if (string.IsNullOrEmpty(SrcDir)) 136 | throw new ArgumentException(nameof(SrcDir)); 137 | if (string.IsNullOrEmpty(DstDir)) 138 | throw new ArgumentException(nameof(DstDir)); 139 | 140 | if (Files.Length == 0) 141 | throw new ArgumentException(nameof(Files)); 142 | 143 | if (!VWrapperFileName.EndsWith(".v")) 144 | VWrapperFileName = VWrapperFileName + ".v"; 145 | 146 | if (string.IsNullOrEmpty(CDeclarationFileName)) 147 | CDeclarationFileName = "c.v"; 148 | 149 | if (!CDeclarationFileName.EndsWith(".v")) 150 | CDeclarationFileName = CDeclarationFileName + ".v"; 151 | 152 | // exlude filenames dont need extensions 153 | ExcludedFiles = ExcludedFiles.Select(f => f.Replace(".h", "")).ToArray(); 154 | ExcludedFromVWrapperFiles = ExcludedFromVWrapperFiles.Select(f => f.Replace(".h", "")).ToArray(); 155 | } 156 | 157 | void AddSystemIncludes() 158 | { 159 | if (TargetSystem == "darwin") 160 | { 161 | SystemIncludeFolders = SystemIncludeFolders.Union(new string[] { 162 | "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include", 163 | "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include", 164 | "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/11.0.0/include" 165 | }).Distinct().ToArray(); 166 | } 167 | } 168 | 169 | /// 170 | /// Retuns all Files as absolute paths. If a file path is relative, it will try to resolve its location using 171 | /// the IncludeFolders and the SrcDir. 172 | /// 173 | public List GetFiles() 174 | => Files.Select(p => Path.IsPathRooted(p) ? p : IncludedFileToAbsPath(p)).ToList(); 175 | 176 | /// 177 | /// If any of paths are relative, returns the path appended to SrcDir. 178 | /// 179 | string[] ToAbsolutePaths(string[] paths) 180 | => paths.Select(p => Path.IsPathRooted(p) ? p : Path.Combine(SrcDir, p)).ToArray(); 181 | 182 | /// 183 | /// Attempts to resolve an included file path by checking all the IncludeFolder and SrcDir to get an absolute 184 | /// path to the file. 185 | /// 186 | string IncludedFileToAbsPath(string path) 187 | { 188 | foreach (var incPath in ToAbsolutePaths(IncludeFolders)) 189 | { 190 | var tmp = Path.Combine(incPath, path); 191 | if (File.Exists(tmp)) 192 | return tmp; 193 | } 194 | 195 | if (!Path.IsPathRooted(SrcDir)) 196 | SrcDir = SrcDir.Replace("~", Environment.GetFolderPath(Environment.SpecialFolder.Personal)); 197 | 198 | var newPath = Path.Combine(SrcDir, path); 199 | if (!File.Exists(newPath)) 200 | throw new FileNotFoundException($"Could not find file {path} from the Files array. Maybe your {nameof(IncludeFolders)} are not correct?"); 201 | return newPath; 202 | } 203 | } 204 | 205 | public static class ConfigExt 206 | { 207 | public static bool IsFunctionExcluded(this Config config, string function) 208 | { 209 | return config.ExcludeFunctionsThatContain.Where(exclude => function.Contains(exclude)).Any(); 210 | } 211 | 212 | public static string StripFunctionPrefix(this Config config, string function) 213 | { 214 | var prefixes = config.StripPrefixFromFunctionNames.Where(p => function.StartsWith(p)); 215 | if (prefixes.Count() > 0) 216 | { 217 | var longestPrefix = prefixes.Aggregate("", (max, cur) => max.Length > cur.Length ? max : cur); 218 | return function.Replace(longestPrefix, ""); 219 | } 220 | return function; 221 | } 222 | 223 | public static bool IsFileExcluded(this Config config, ParsedFile file) 224 | { 225 | return config.ExcludedFiles.Contains(file.Filename) 226 | || config.ExcludedFiles.Contains(Path.Combine(file.Folder, file.Filename)); 227 | } 228 | 229 | public static bool IsFileExcludedFromVWrapper(this Config config, ParsedFile file) 230 | { 231 | return config.ExcludedFromVWrapperFiles.Contains(file.Filename) 232 | || config.ExcludedFromVWrapperFiles.Contains(Path.Combine(file.Folder, file.Filename)); 233 | } 234 | } 235 | } -------------------------------------------------------------------------------- /src/CppPrimitiveTypeExt.cs: -------------------------------------------------------------------------------- 1 | using CppAst; 2 | 3 | namespace Generator 4 | { 5 | public static class CppPrimitiveTypeExt 6 | { 7 | public static string GetVType(this CppPrimitiveType self) 8 | { 9 | switch (self.Kind) 10 | { 11 | case CppPrimitiveKind.Bool: 12 | return "bool"; 13 | case CppPrimitiveKind.Char: 14 | return "byte"; 15 | case CppPrimitiveKind.Double: 16 | return "f64"; 17 | case CppPrimitiveKind.Float: 18 | return "f32"; 19 | case CppPrimitiveKind.Int: 20 | return "int"; 21 | case CppPrimitiveKind.LongDouble: 22 | throw new System.NotImplementedException(); 23 | case CppPrimitiveKind.LongLong: 24 | return "i64"; 25 | case CppPrimitiveKind.Short: 26 | return "i16"; 27 | case CppPrimitiveKind.UnsignedChar: 28 | return "byte"; 29 | case CppPrimitiveKind.UnsignedInt: 30 | return "u32"; 31 | case CppPrimitiveKind.UnsignedLongLong: 32 | return "u64"; 33 | case CppPrimitiveKind.UnsignedShort: 34 | return "u16"; 35 | case CppPrimitiveKind.Void: 36 | return "void"; 37 | case CppPrimitiveKind.WChar: 38 | return "bool"; 39 | } 40 | throw new System.NotImplementedException(); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/CppSourceSpanExt.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using CppAst; 3 | 4 | namespace Generator 5 | { 6 | public static class CppSourceSpanExt 7 | { 8 | public static string FilePath(this CppSourceSpan cppSpan) 9 | { 10 | var span = cppSpan.ToString(); 11 | return span.Substring(0, span.IndexOf("(")); 12 | } 13 | 14 | public static string Filename(this CppSourceSpan cppSpan) 15 | { 16 | return Path.GetFileName(cppSpan.FilePath()); 17 | } 18 | 19 | public static string FilenameNoExtension(this CppSourceSpan cppSpan) 20 | { 21 | return cppSpan.Filename().Replace(".h", ""); 22 | } 23 | 24 | public static string Folder(this CppSourceSpan cppSpan) 25 | { 26 | return Path.GetFileName(Path.GetDirectoryName(cppSpan.FilePath())); 27 | } 28 | 29 | public static string FolderFromBaseSrcFolder(this CppSourceSpan cppSpan, string baseSrcFolder) 30 | { 31 | var path = cppSpan.FilePath(); 32 | if (cppSpan.FilePath().IndexOf(baseSrcFolder) == -1) 33 | return Path.GetFileName(Path.GetDirectoryName(path)); 34 | return Path.GetDirectoryName(path.Substring(path.IndexOf(baseSrcFolder) + baseSrcFolder.Length + 1)); 35 | } 36 | 37 | public static void CopyTo(this CppSourceSpan cppSpan, Config config) 38 | { 39 | var dst = Path.Combine(config.DstDir, "thirdparty", config.BaseSourceFolder, cppSpan.FolderFromBaseSrcFolder(config.BaseSourceFolder)); 40 | 41 | // we could have a header outside of our BaseSourceFolder. in that case stick it in a folder in thirdparty 42 | if (cppSpan.FilePath().IndexOf(config.BaseSourceFolder) == -1) 43 | dst = Path.Combine(config.DstDir, "thirdparty", cppSpan.FolderFromBaseSrcFolder(config.BaseSourceFolder)); 44 | Directory.CreateDirectory(dst); 45 | 46 | File.Copy(cppSpan.FilePath(), Path.Combine(dst, cppSpan.Filename()), true); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/CppTypedefExt.cs: -------------------------------------------------------------------------------- 1 | using CppAst; 2 | 3 | namespace Generator 4 | { 5 | public static class CppTypedefExt 6 | { 7 | public static bool IsPrimitiveType(this CppTypedef typedef) 8 | => typedef.ElementType.TypeKind == CppTypeKind.Primitive; 9 | 10 | public static CppPrimitiveType ElementTypeAsPrimitive(this CppTypedef typedef) 11 | => typedef.ElementType as CppPrimitiveType; 12 | 13 | public static bool IsFunctionType(this CppTypedef typedef) 14 | { 15 | // search the ElementType hiearchy for CppTypedefs and CppPointers until we finally get to a CppFunction or not 16 | if (typedef.ElementType is CppTypedef td) 17 | return td.IsFunctionType(); 18 | 19 | if (typedef.ElementType is CppPointerType ptr) 20 | { 21 | if (ptr.ElementType.TypeKind == CppTypeKind.Function) 22 | return true; 23 | } 24 | 25 | if (typedef.ElementType.TypeKind == CppTypeKind.Function) 26 | return true; 27 | 28 | return false; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/Debug.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using CppAst; 5 | 6 | namespace Generator 7 | { 8 | public static class Debug 9 | { 10 | static bool _printComments; 11 | 12 | public static void Dump(this CppCompilation comp, bool printComments = false) 13 | { 14 | var typeMap = new TypedefMap(comp.Typedefs); 15 | _printComments = printComments; 16 | 17 | if (comp.Diagnostics.Messages.Count > 0) 18 | Console.WriteLine("------ Messages ------"); 19 | foreach (var message in comp.Diagnostics.Messages) 20 | { 21 | Console.WriteLine(message); 22 | } 23 | 24 | if (comp.Macros.Count > 0) 25 | Console.WriteLine("\n------ Macros ------"); 26 | foreach (var macro in comp.Macros) 27 | { 28 | Console.WriteLine(macro); 29 | } 30 | 31 | if (comp.Typedefs.Count > 0) 32 | Console.WriteLine("\n------ Typedefs ------"); 33 | foreach (var typedef in comp.Typedefs) 34 | { 35 | PrintComment(typedef.Comment); 36 | Console.WriteLine(typedef); 37 | } 38 | 39 | if (comp.Enums.Count > 0) 40 | Console.WriteLine("\n------ Enums ------"); 41 | foreach (var @enum in comp.Enums) 42 | { 43 | PrintComment(@enum.Comment); 44 | Console.WriteLine($"enum {typeMap.GetOrNot(@enum.Name)}"); 45 | Console.WriteLine($"\tType: {@enum.IntegerType}"); 46 | 47 | foreach (var t in @enum.Items) 48 | Console.WriteLine($"\t{t}"); 49 | 50 | if (comp.Enums.Last() != @enum) 51 | Console.WriteLine(); 52 | } 53 | 54 | if (comp.Functions.Count > 0) 55 | Console.WriteLine("\n------ Functions ------"); 56 | foreach (var cppFunction in comp.Functions) 57 | { 58 | PrintComment(cppFunction.Comment); 59 | Console.WriteLine(cppFunction); 60 | } 61 | 62 | if (comp.Classes.Count > 0) 63 | Console.WriteLine("\n------ Structs ------"); 64 | foreach (var cppClass in comp.Classes) 65 | { 66 | if (cppClass.ClassKind != CppClassKind.Struct) 67 | { 68 | Console.WriteLine($"Error: found a non-struct type! {cppClass.ClassKind} - {cppClass.Name}"); 69 | } 70 | PrintComment(cppClass.Comment); 71 | Console.WriteLine($"struct {cppClass.Name}"); 72 | foreach (var field in cppClass.Fields) 73 | { 74 | if (field.Type.TypeKind == CppTypeKind.Array) 75 | Console.WriteLine($"\t-- array --"); 76 | 77 | if (field.Type.TypeKind == CppTypeKind.StructOrClass && field.Parent is CppClass parent) 78 | { 79 | Console.WriteLine($"\t{parent.Name} {field.Name}"); 80 | } 81 | else 82 | { 83 | var typeName = field.Type.ToString(); 84 | if (field.Type.TypeKind == CppTypeKind.Typedef) 85 | { 86 | var t = field.Type as CppTypedef; 87 | typeName = t.Name; 88 | } 89 | 90 | Console.WriteLine($"\t{typeName} {field.Name}"); 91 | } 92 | } 93 | 94 | if (comp.Classes.Last() != cppClass) 95 | Console.WriteLine(); 96 | } 97 | } 98 | 99 | static string FilenameFromSpan(CppSourceSpan cppSpan) 100 | { 101 | var span = cppSpan.ToString(); 102 | span = span.Substring(0, span.IndexOf("(")); 103 | return Path.GetFileName(span); 104 | } 105 | 106 | static void PrintComment(CppComment comment) 107 | { 108 | if (!_printComments || comment == null) 109 | return; 110 | 111 | if (!string.IsNullOrEmpty(comment.ToString())) 112 | Console.WriteLine($"// {comment}"); 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /src/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using CppAst; 4 | using System.Text.Json; 5 | using System.IO; 6 | 7 | namespace Generator 8 | { 9 | class Program 10 | { 11 | static void Main(string[] args) 12 | { 13 | if (args.Contains("-h")) 14 | { 15 | PrintHelp(); 16 | } 17 | else if (args.Contains("-c")) 18 | { 19 | if (args.Length != 2) 20 | { 21 | Console.WriteLine("Invalid arguments. The -c option requires a filename"); 22 | PrintHelp(); 23 | return; 24 | } 25 | var index = Array.IndexOf(args, "-c"); 26 | var file = args[index + 1]; 27 | 28 | var options = new JsonSerializerOptions 29 | { 30 | WriteIndented = true 31 | }; 32 | var json = JsonSerializer.Serialize(GetSDLConfig(), options); 33 | File.WriteAllText(file, json); 34 | } 35 | else if (args.Contains("-g")) 36 | { 37 | if (args.Length != 2) 38 | { 39 | Console.WriteLine("Invalid arguments. The -g option requires a filename"); 40 | PrintHelp(); 41 | return; 42 | } 43 | var index = Array.IndexOf(args, "-g"); 44 | var file = args[index + 1]; 45 | 46 | var config = JsonSerializer.Deserialize(File.ReadAllText(file)); 47 | Run(config); 48 | } 49 | else 50 | { 51 | PrintHelp(); 52 | 53 | // Run(GetKincConfig()); 54 | // Run(GetPhyFSConfig()); 55 | // Run(GetSDLConfig()); 56 | // Run(GetLuaConfig()); 57 | // Run(GetFlecsConfig()); 58 | } 59 | } 60 | 61 | private static void PrintHelp() 62 | { 63 | Console.WriteLine("VGenerator Help"); 64 | Console.WriteLine("Standard usage pattern is to first use the '-c' option to create a template generator JSON file."); 65 | Console.WriteLine("The generator JSON file will have SDL2 data in it as an example so that you can see what the params look like."); 66 | Console.WriteLine("Fill in the details of the JSON file then use the '-g' option to generate the V bindings."); 67 | Console.WriteLine("\nUsage:"); 68 | Console.WriteLine("\tWrite an empty generator configuration json file:"); 69 | Console.WriteLine("\tVGenerator -c FILENAME"); 70 | Console.WriteLine("\n\tGenerate V bindings from a generator configuration json file:"); 71 | Console.WriteLine("\tVGenerator -g CONFIG_FILENAME"); 72 | } 73 | 74 | static void Run(Config config) 75 | { 76 | var compilation = CppParser.ParseFiles(config.GetFiles(), config.ToParserOptions()); 77 | VGenerator.Generate(config, compilation); 78 | // compilation.Dump(); 79 | } 80 | 81 | #region Example Configs 82 | 83 | static Config GetLuaConfig() 84 | { 85 | return new Config 86 | { 87 | DstDir = "~/Desktop/lua", 88 | SrcDir = "~/Desktop/lua-5.3.5/src", 89 | BaseSourceFolder = "src", 90 | ModuleName = "lua", 91 | VWrapperFileName = "lua", 92 | SingleVFileExport = true, 93 | ExcludeFunctionsThatContain = new string[] {}, 94 | StripPrefixFromFunctionNames = new string[] {}, 95 | CTypeToVType = { 96 | {"__sFILE", "voidptr"} 97 | }, 98 | Defines = new string[] {}, 99 | IncludeFolders = new string[] { "src" }, 100 | Files = new string[] { 101 | "lua.h", "lualib.h", "lauxlib.h" 102 | }, 103 | ExcludedFiles = new string[] {}, 104 | ExcludedFromVWrapperFiles = new string[] {} 105 | }; 106 | } 107 | 108 | static Config GetFlecsConfig() 109 | { 110 | return new Config 111 | { 112 | DstDir = "~/Desktop/flecs/flecs", 113 | SrcDir = "~/.vmodules/prime31/flecs/flecs_git/src", 114 | BaseSourceFolder = "src", 115 | ModuleName = "flecs", 116 | VWrapperFileName = "flecs", 117 | SingleVFileExport = false, 118 | ExcludeFunctionsThatContain = new string[] {}, 119 | StripPrefixFromFunctionNames = new string[] {}, 120 | CTypeToVType = {}, 121 | Defines = new string[] { "FLECS_NO_CPP" }, 122 | IncludeFolders = new string[] { 123 | "~/.vmodules/prime31/flecs/flecs_git/include", 124 | "~/.vmodules/prime31/flecs/flecs_git/include/flecs", 125 | "~/.vmodules/prime31/flecs/flecs_git/include/flecs/util" 126 | }, 127 | Files = new string[] { 128 | "flecs.h" 129 | }, 130 | ExcludedFiles = new string[] {}, 131 | ExcludedFromVWrapperFiles = new string[] {} 132 | }; 133 | } 134 | 135 | static Config GetSDLConfig() 136 | { 137 | return new Config 138 | { 139 | DstDir = "~/Desktop/SDL2/sdl", 140 | SrcDir = "/usr/local/include/SDL2", 141 | BaseSourceFolder = "src", 142 | ModuleName = "sdl", 143 | VWrapperFileName = "sdl", 144 | SingleVFileExport = true, 145 | ExcludeFunctionsThatContain = new string[] {}, 146 | StripPrefixFromFunctionNames = new string[] { "SDL_"}, 147 | CTypeToVType = { 148 | {"__sFILE", "voidptr"} 149 | }, 150 | Defines = new string[] {}, 151 | IncludeFolders = new string[] {}, 152 | Files = new string[] { 153 | "SDL.h" 154 | }, 155 | ExcludedFiles = new string[] { 156 | "SDL_main", "SDL_audio", "SDL_assert", "SDL_atomic", "SDL_mutex", 157 | "SDL_thread", "SDL_gesture", "SDL_sensor", "SDL_power", "SDL_render", "SDL_shape", 158 | "SDL_endian", "SDL_cpuinfo", "SDL_loadso", "SDL_system" 159 | }, 160 | ExcludedFromVWrapperFiles = new string[] { 161 | "SDL_stdinc" 162 | } 163 | }; 164 | } 165 | 166 | static Config GetPhyFSConfig() 167 | { 168 | return new Config 169 | { 170 | DstDir = "~/Desktop/PhysFSGen", 171 | SrcDir = "~/Desktop/physfs/src", 172 | BaseSourceFolder = "src", 173 | ModuleName = "c", 174 | VWrapperFileName = "physfs", 175 | SingleVFileExport = true, 176 | ExcludeFunctionsThatContain = new string[] {}, 177 | StripPrefixFromFunctionNames = new string[] { "PHYSFS_"}, 178 | CTypeToVType = {}, 179 | TargetSystem = "darwin", 180 | Defines = new string[] {}, 181 | IncludeFolders = new string[] {}, 182 | Files = new string[] { 183 | "physfs.h" 184 | } 185 | }; 186 | } 187 | 188 | static Config GetKincConfig() 189 | { 190 | return new Config 191 | { 192 | DstDir = "~/Desktop/KincGen", 193 | SrcDir = "~/Desktop/kha/Shader-Kinc/Kinc", 194 | BaseSourceFolder = "kinc", 195 | ModuleName = "c", 196 | SingleVFileExport = true, 197 | ExcludeFunctionsThatContain = new string[] { "_internal_" }, 198 | StripPrefixFromFunctionNames = new string[] { "kinc_g4_", "kinc_g5_", "kinc_", "LZ4_" }, 199 | CTypeToVType = { 200 | {"kinc_ticks_t", "u64"}, 201 | {"id", "voidptr"} 202 | }, 203 | TargetSystem = "darwin", 204 | Defines = new string[] { 205 | "KORE_MACOS", "KORE_METAL", "KORE_POSIX", "KORE_G1", "KORE_G2", "KORE_G3", "KORE_G4", 206 | "KORE_G5", "KORE_G4ONG5", "KORE_MACOS", "KORE_METAL", "KORE_POSIX", "KORE_A1", "KORE_A2", 207 | "KORE_A3", "KORE_NO_MAIN" 208 | }, 209 | IncludeFolders = new string[] { 210 | "Sources", 211 | "Backends/System/Apple/Sources", 212 | "Backends/System/macOS/Sources", 213 | "Backends/System/POSIX/Sources", 214 | "Backends/Graphics5/Metal/Sources", 215 | "Backends/Graphics4/G4onG5/Sources", 216 | "Backends/Audio3/A3onA2/Sources" 217 | }, 218 | Files = new string[] { 219 | "kinc/graphics1/graphics.h", 220 | "kinc/graphics4/constantlocation.h", 221 | "kinc/graphics4/graphics.h", 222 | "kinc/graphics4/indexbuffer.h", 223 | "kinc/graphics4/rendertarget.h", 224 | "kinc/graphics4/shader.h", 225 | "kinc/graphics4/texture.h", 226 | "kinc/graphics4/texturearray.h", 227 | "kinc/graphics4/textureunit.h", 228 | "kinc/graphics5/commandlist.h", 229 | "kinc/graphics5/constantbuffer.h", 230 | "kinc/input/gamepad.h", 231 | "kinc/input/keyboard.h", 232 | "kinc/input/mouse.h", 233 | "kinc/input/surface.h", 234 | "kinc/io/filereader.h", 235 | "kinc/io/filewriter.h", 236 | "kinc/math/random.h", 237 | "kinc/system.h", 238 | "kinc/window.h" 239 | } 240 | }; 241 | } 242 | 243 | #endregion 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/TypedefMap.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using CppAst; 3 | 4 | namespace Generator 5 | { 6 | public class TypedefMap 7 | { 8 | public Dictionary Map = new Dictionary(); 9 | 10 | public TypedefMap(CppContainerList types) 11 | { 12 | foreach (var t in types) 13 | { 14 | Add(t.ElementType.GetDisplayName(), t.Name); 15 | Add(t.Name, t.ElementType.GetDisplayName()); 16 | 17 | if (t.IsPrimitiveType()) 18 | V.AddTypeConversion(t.Name, t.ElementTypeAsPrimitive().GetVType()); 19 | } 20 | } 21 | 22 | public void Add(string from, string to) => Map[from] = to; 23 | 24 | public bool Has(string t) => Map.ContainsKey(t); 25 | 26 | public bool TryGet(string t, out string newType) => Map.TryGetValue(t, out newType); 27 | 28 | public string GetOrNot(string t) => Has(t) ? Map[t] : t; 29 | } 30 | } -------------------------------------------------------------------------------- /src/VGenerator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | netcoreapp3.0 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/VGenerator/ParsedFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using CppAst; 6 | 7 | namespace Generator 8 | { 9 | public class ParsedFile 10 | { 11 | public string Filename; 12 | public string Folder; 13 | public List Typedefs = new List(); 14 | public List Enums = new List(); 15 | public List Functions = new List(); 16 | public List ParsedFunctions = new List(); 17 | public List Structs = new List(); 18 | 19 | public ParsedFile(string filename, string folder) 20 | { 21 | Filename = filename; 22 | Folder = folder ?? ""; 23 | } 24 | 25 | 26 | public static List ParseIntoFiles(CppCompilation comp, Config config) 27 | { 28 | var spans = new HashSet(); 29 | var files = new List(); 30 | 31 | foreach (var typedef in comp.Typedefs) 32 | { 33 | var file = GetOrCreateFile(files, typedef.Span, false); 34 | file.Typedefs.Add(typedef); 35 | spans.Add(typedef.Span); 36 | } 37 | 38 | foreach (var e in comp.Enums) 39 | { 40 | if (string.IsNullOrEmpty(e.Name)) 41 | { 42 | Console.WriteLine($"Found nameless enum with {e.Items.Count} items! [{e.Span.FilePath()}]"); 43 | continue; 44 | } 45 | 46 | var file = GetOrCreateFile(files, e.Span, false); 47 | file.Enums.Add(e); 48 | spans.Add(e.Span); 49 | } 50 | 51 | foreach (var function in comp.Functions) 52 | { 53 | if (config.IsFunctionExcluded(function.Name)) 54 | continue; 55 | 56 | var file = GetOrCreateFile(files, function.Span, false); 57 | file.Functions.Add(function); 58 | file.ParsedFunctions.Add(ParsedFunctionFromCppFunction(function, config)); 59 | spans.Add(function.Span); 60 | } 61 | 62 | foreach (var klass in comp.Classes) 63 | { 64 | var file = GetOrCreateFile(files, klass.Span, false); 65 | file.Structs.Add(klass); 66 | spans.Add(klass.Span); 67 | } 68 | 69 | if (config.CopyHeadersToDstDir) 70 | { 71 | foreach (var span in spans) 72 | span.CopyTo(config); 73 | } 74 | 75 | return files.Where(f => !config.IsFileExcluded(f)).ToList(); 76 | } 77 | 78 | static ParsedFile GetOrCreateFile(List files, CppSourceSpan span, bool singleFileExport) 79 | { 80 | if (singleFileExport) 81 | { 82 | if (files.Count == 0) 83 | files.Add(new ParsedFile(null, null)); 84 | return files[0]; 85 | } 86 | 87 | var filename = span.FilenameNoExtension(); 88 | var folder = span.Folder(); 89 | 90 | var file = files.Where(f => f.Filename == filename && f.Folder == folder).FirstOrDefault(); 91 | if (file == null) 92 | { 93 | file = new ParsedFile(filename, folder); 94 | files.Add(file); 95 | } 96 | return file; 97 | } 98 | 99 | /// 100 | /// Deals with turning the CppFunction types into V types, function name to snake case, params 101 | /// to snake case and any other data transformations we will need for the V functions 102 | /// 103 | static ParsedFunction ParsedFunctionFromCppFunction(CppFunction cFunc, Config config) 104 | { 105 | var f = new ParsedFunction 106 | { 107 | Name = cFunc.Name, 108 | VName = V.ToSnakeCase(config.StripFunctionPrefix(cFunc.Name)) 109 | }; 110 | 111 | // hack to fix ghetto forced 'init' module function 112 | if (f.VName == "init") 113 | f.VName = config.ModuleName + "_" + f.VName; 114 | 115 | if (cFunc.ReturnType.GetDisplayName() != "void") 116 | { 117 | f.RetType = cFunc.ReturnType.GetDisplayName(); 118 | f.VRetType = V.GetVType(cFunc.ReturnType); 119 | } 120 | 121 | foreach (var param in cFunc.Parameters) 122 | { 123 | var p = new ParsedParameter 124 | { 125 | Name = param.Name.EscapeReserved(), 126 | VName = V.ToSnakeCase(config.StripFunctionPrefix(param.Name)).EscapeReserved(), 127 | Type = param.Type.GetDisplayName(), 128 | VType = V.GetVType(param.Type) 129 | }; 130 | f.Parameters.Add(p); 131 | } 132 | 133 | return f; 134 | } 135 | } 136 | 137 | public class ParsedFunction 138 | { 139 | public string Name, VName; 140 | public string RetType, VRetType; 141 | public List Parameters = new List(); 142 | } 143 | 144 | public class ParsedParameter 145 | { 146 | public string Name, VName; 147 | public string Type, VType; 148 | } 149 | } -------------------------------------------------------------------------------- /src/VGenerator/V.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | using CppAst; 7 | 8 | namespace Generator 9 | { 10 | public static class V 11 | { 12 | static Dictionary cTypeToVType = new Dictionary 13 | { 14 | {"bool", "bool"}, 15 | {"void*", "voidptr"}, 16 | {"void**", "&voidptr"}, 17 | {"char", "byte"}, 18 | {"char*", "byteptr"}, 19 | {"wchar", "u16"}, 20 | {"int8_t", "i8"}, 21 | {"short", "i16"}, 22 | {"int16_t", "i16"}, 23 | {"size_t", "size_t"}, 24 | {"int", "int"}, 25 | {"int*", "&int"}, 26 | {"int32_t", "int"}, 27 | {"int64_t", "i64"}, 28 | {"long", "i64"}, 29 | {"float", "f32"}, 30 | {"double", "f64"}, 31 | {"uint8_t*", "&byte"}, 32 | {"uint8_t", "byte"}, 33 | {"uint16_t", "u16"}, 34 | {"unsigned short", "u16"}, 35 | {"uint32_t", "u32"}, 36 | {"unsigned int", "u32"}, 37 | {"unsigned long", "u64"}, 38 | {"uint64_t", "u64"}, 39 | {"unsigned char", "byte"}, 40 | {"const char *", "byteptr"}, 41 | {"const char*", "byteptr"}, 42 | {"const void *", "voidptr"}, 43 | {"const void*", "voidptr"}, 44 | {"unsigned char*", "byteptr"}, 45 | {"unsigned char *", "byteptr"}, 46 | {"const char**", "&voidptr"} 47 | }; 48 | 49 | static string[] reserved = new[] { /*"map",*/ "string", "return", "or", "none", "type", "select", "false", "true", "module" }; 50 | 51 | public static void AddTypeConversions(Dictionary types) 52 | { 53 | foreach (var t in types) 54 | { 55 | if (!cTypeToVType.ContainsKey(t.Value)) 56 | cTypeToVType[t.Key] = t.Value; 57 | } 58 | } 59 | 60 | public static void AddTypeConversion(string type) => cTypeToVType[type] = "C." + type; 61 | 62 | public static void AddTypeConversion(string type, string toType) => cTypeToVType[type] = toType; 63 | 64 | public static string GetVType(CppType cppType) 65 | { 66 | // unwrap any const vars 67 | if (cppType.TypeKind == CppTypeKind.Qualified && cppType is CppQualifiedType cppQualType) 68 | { 69 | if (cppQualType.Qualifier == CppTypeQualifier.Const) 70 | return GetVType(cppQualType.ElementType); 71 | } 72 | 73 | if (cppType is CppClass cppClass && cppClass.ClassKind == CppClassKind.Union) 74 | { 75 | Console.WriteLine($"Found union we can't handle! [{cppType.Span}]"); 76 | return "voidptr"; 77 | } 78 | 79 | if (cppType.TypeKind == CppTypeKind.Enum || cppType.TypeKind == CppTypeKind.Primitive) 80 | return GetVType(cppType.GetDisplayName()); 81 | 82 | if (cppType.TypeKind == CppTypeKind.Typedef && cppType is CppTypedef typeDefType) 83 | { 84 | if (typeDefType.IsPrimitiveType()) 85 | return typeDefType.ElementTypeAsPrimitive().GetVType(); 86 | else 87 | return GetVType(typeDefType.ElementType); 88 | } 89 | 90 | if (cppType.TypeKind == CppTypeKind.Pointer) 91 | { 92 | var cppPtrType = cppType as CppPointerType; 93 | 94 | // special V types 95 | if (cppPtrType.GetDisplayName() == "const char*" || cppPtrType.GetDisplayName() == "char*") 96 | return "byteptr"; 97 | 98 | if (cppPtrType.GetDisplayName() == "const void*" || cppPtrType.GetDisplayName() == "void*") 99 | return "voidptr"; 100 | 101 | // double pointer check 102 | if (cppPtrType.ElementType.TypeKind == CppTypeKind.Pointer) 103 | { 104 | if (cppPtrType.ElementType.TypeKind == CppTypeKind.Pointer) 105 | return $"&voidptr /* {cppPtrType.GetDisplayName()} */"; 106 | return $"&{GetVType(cppPtrType.ElementType)} /* {cppPtrType.GetDisplayName()} */"; 107 | } 108 | 109 | // unwrap any const vars 110 | if (cppPtrType.ElementType.TypeKind == CppTypeKind.Qualified && cppPtrType.ElementType is CppQualifiedType qualType) 111 | { 112 | if (qualType.Qualifier == CppTypeQualifier.Const) 113 | { 114 | if (qualType.ElementType is CppPrimitiveType qualPrimType && qualPrimType.Kind == CppPrimitiveKind.Void) 115 | return $"voidptr"; 116 | return "&" + GetVType(qualType.ElementType); 117 | } 118 | } 119 | 120 | // function pointers 121 | if (cppPtrType.ElementType.TypeKind == CppTypeKind.Function) 122 | { 123 | var funcType = cppPtrType.ElementType as CppFunctionType; 124 | if (funcType.Parameters.Count == 1 && funcType.Parameters[0].Type is CppPointerType cppPtrPtrType && cppPtrPtrType.ElementType.TypeKind == CppTypeKind.Function) 125 | funcType = cppPtrPtrType.ElementType as CppFunctionType; 126 | 127 | // HACK: for some reason, there can occassionally be function pointer args inside a function pointer argument 128 | // for some reason. We just peel away the top function pointer and use that as the full type to fix the issue. 129 | foreach (var p in funcType.Parameters) 130 | { 131 | if (p.Type is CppPointerType pType && pType.ElementType.TypeKind == CppTypeKind.Function) 132 | funcType = pType.ElementType as CppFunctionType; 133 | } 134 | 135 | string GetReturnType() 136 | { 137 | // void return 138 | if (funcType.ReturnType is CppPrimitiveType cppPrimType && cppPrimType.Kind == CppPrimitiveKind.Void) 139 | return null; 140 | return GetVType(funcType.ReturnType); 141 | }; 142 | 143 | // easy case: no parameters 144 | if (funcType.Parameters.Count == 0) 145 | return $"fn() {GetReturnType()}".TrimEnd(); 146 | 147 | var sb = new StringBuilder(); 148 | sb.Append("fn("); 149 | foreach (var p in funcType.Parameters) 150 | { 151 | var paramType = GetVType(p.Type); 152 | if (paramType.Contains("fn")) 153 | { 154 | // TODO: typedef the function param 155 | var typeDef = $"pub type Fn{V.ToPascalCase(p.Name)} {paramType}"; 156 | } 157 | sb.Append(paramType); 158 | 159 | if (funcType.Parameters.Last() != p) 160 | sb.Append(", "); 161 | } 162 | sb.Append(")"); 163 | 164 | var ret = GetReturnType(); 165 | if (ret != null) 166 | sb.Append($" {ret}"); 167 | 168 | // check for a function pointer that has a function as a param. This is currently invalid in V. 169 | var definition = sb.ToString(); 170 | if (definition.LastIndexOf("fn") > 2) 171 | return $"voidptr /* {definition} */"; 172 | return sb.ToString(); 173 | } 174 | else if (cppPtrType.ElementType.TypeKind == CppTypeKind.Typedef) 175 | { 176 | // functions dont get passed with '&' so we have to see if this Typedef has a function in its lineage 177 | if (cppPtrType.ElementType is CppTypedef td && td.IsFunctionType()) 178 | return GetVType(cppPtrType.ElementType); 179 | return "&" + GetVType(cppPtrType.ElementType); 180 | } 181 | 182 | return "&" + GetVType(cppPtrType.ElementType.GetDisplayName()); 183 | } // end Pointer 184 | 185 | if (cppType.TypeKind == CppTypeKind.Array) 186 | { 187 | var arrType = cppType as CppArrayType; 188 | if (arrType.ElementType is CppClass arrParamClass) 189 | { 190 | if (arrParamClass.Name.Contains("va_")) 191 | { 192 | Console.WriteLine($"Found unhandled vararg param! [{cppType}]"); 193 | return "voidptr /* ...voidptr */"; 194 | } 195 | } 196 | var eleType = GetVType(arrType.ElementType); 197 | if (arrType.Size > 0) 198 | return $"[{arrType.Size}]{eleType}"; 199 | return $"[]{eleType}"; 200 | } 201 | 202 | return GetVType(cppType.GetDisplayName()); 203 | } 204 | 205 | public static string GetVType(string type) 206 | { 207 | if (cTypeToVType.TryGetValue(type, out var vType)) 208 | return vType; 209 | 210 | Console.WriteLine($"no conversion found for {type}"); 211 | return type; 212 | } 213 | 214 | public static string GetVEnumName(string name) 215 | { 216 | if (name.EndsWith("_t")) 217 | name = name.Substring(0, name.Length - 2); 218 | 219 | return ToPascalCase(name); 220 | } 221 | 222 | public static string GetCFieldName(string name) 223 | { 224 | if (reserved.Contains(name)) 225 | return "@" + name; 226 | return name; 227 | } 228 | 229 | public static string GetVEnumItemName(string name) 230 | { 231 | if (name.Contains('_')) 232 | return name.ToLower().MakeSafeEnumItem(); 233 | 234 | if (name.IsUpper()) 235 | name = name.ToLower().MakeSafeEnumItem(); 236 | 237 | return ToSnakeCase(name).MakeSafeEnumItem(); 238 | } 239 | 240 | /// 241 | /// escapes a reserved name for use as a parameter. 242 | /// 243 | public static string EscapeReserved(this string name) 244 | { 245 | if (name == "none") 246 | return "non"; 247 | 248 | if (name == "type") 249 | return "typ"; 250 | 251 | if (name == "false") 252 | return "no"; 253 | 254 | if (name == "true") 255 | return "yes"; 256 | 257 | if (name == "return") 258 | return "ret"; 259 | 260 | if (name == "select") 261 | return "sel"; 262 | 263 | if (name == "module") 264 | return "mod"; 265 | 266 | if (reserved.Contains(name)) 267 | throw new System.Exception($"need escape for {name}"); 268 | return name; 269 | } 270 | 271 | /// 272 | /// escapes a reserved field name with a @ for correct functionality with C names 273 | /// 274 | public static string EscapeReservedField(string name) 275 | { 276 | if (reserved.Contains(name)) 277 | return $"@{name}"; 278 | return name; 279 | } 280 | 281 | public static string ToSnakeCase(string name) 282 | { 283 | if (name.IsLower()) 284 | return name; 285 | 286 | if (name.Contains("_")) 287 | return name.ToLower(); 288 | 289 | name = string.Concat(name.Select((x, i) => i > 0 && char.IsUpper(x) ? "_" + x.ToString() : x.ToString())).ToLower(); 290 | return EscapeReserved(name); 291 | } 292 | 293 | public static string ToPascalCase(string original) 294 | { 295 | var invalidCharsRgx = new Regex("[^_a-zA-Z0-9]"); 296 | var whiteSpace = new Regex(@"(?<=\s)"); 297 | var startsWithLowerCaseChar = new Regex("^[a-z]"); 298 | var firstCharFollowedByUpperCasesOnly = new Regex("(?<=[A-Z])[A-Z0-9]+$"); 299 | var lowerCaseNextToNumber = new Regex("(?<=[0-9])[a-z]"); 300 | var upperCaseInside = new Regex("(?<=[A-Z])[A-Z]+?((?=[A-Z][a-z])|(?=[0-9]))"); 301 | 302 | // replace white spaces with undescore, then replace all invalid chars with empty string 303 | var pascalCase = invalidCharsRgx.Replace(whiteSpace.Replace(original, "_"), string.Empty) 304 | // split by underscores 305 | .Split(new char[] { '_' }, StringSplitOptions.RemoveEmptyEntries) 306 | // set first letter to uppercase 307 | .Select(w => startsWithLowerCaseChar.Replace(w, m => m.Value.ToUpper())) 308 | // replace second and all following upper case letters to lower if there is no next lower (ABC -> Abc) 309 | .Select(w => firstCharFollowedByUpperCasesOnly.Replace(w, m => m.Value.ToLower())) 310 | // set upper case the first lower case following a number (Ab9cd -> Ab9Cd) 311 | .Select(w => lowerCaseNextToNumber.Replace(w, m => m.Value.ToUpper())) 312 | // lower second and next upper case letters except the last if it follows by any lower (ABcDEf -> AbcDef) 313 | .Select(w => upperCaseInside.Replace(w, m => m.Value.ToLower())); 314 | 315 | return string.Concat(pascalCase); 316 | } 317 | 318 | public static bool IsUpper(this string value) 319 | { 320 | for (int i = 0; i < value.Length; i++) 321 | { 322 | if (char.IsLower(value[i])) 323 | return false; 324 | } 325 | return true; 326 | } 327 | 328 | public static bool IsLower(this string value) 329 | { 330 | for (int i = 0; i < value.Length; i++) 331 | { 332 | if (char.IsUpper(value[i]) && value[i] != '_') 333 | return false; 334 | } 335 | return true; 336 | } 337 | 338 | public static string MakeSafeEnumItem(this string name) 339 | { 340 | if (char.IsDigit(name[0])) 341 | return "_" + name; 342 | 343 | return EscapeReserved(name); 344 | } 345 | } 346 | } -------------------------------------------------------------------------------- /src/VGenerator/VGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using CppAst; 5 | 6 | namespace Generator 7 | { 8 | public static class VGenerator 9 | { 10 | public static void Generate(Config config, CppCompilation comp) 11 | { 12 | var typeMap = new TypedefMap(comp.Typedefs); 13 | 14 | V.AddTypeConversions(config.CTypeToVType); 15 | 16 | // add conversions for any types in the lib 17 | foreach (var s in comp.Classes) 18 | { 19 | var mappedName = typeMap.GetOrNot(s.Name); 20 | if (mappedName != s.Name) 21 | V.AddTypeConversion(s.Name, mappedName); 22 | V.AddTypeConversion(typeMap.GetOrNot(s.Name)); 23 | } 24 | 25 | // enums will all be replaced by our V enums 26 | foreach (var e in comp.Enums) 27 | { 28 | var mappedName = typeMap.GetOrNot(e.Name); 29 | if (mappedName != e.Name) 30 | V.AddTypeConversion(mappedName, V.GetVEnumName(config.StripFunctionPrefix(e.Name))); 31 | V.AddTypeConversion(e.Name, V.GetVEnumName(config.StripFunctionPrefix(e.Name))); 32 | } 33 | 34 | Directory.CreateDirectory(config.DstDir); 35 | 36 | StreamWriter writer = null; 37 | if (config.SingleVFileExport) 38 | { 39 | writer = new StreamWriter(File.Open(Path.Combine(config.DstDir, config.CDeclarationFileName), FileMode.Create)); 40 | writer.WriteLine($"module {config.ModuleName}"); 41 | } 42 | 43 | var parsedFiles = ParsedFile.ParseIntoFiles(comp, config); 44 | foreach (var file in parsedFiles) 45 | WriteFile(config, file, writer); 46 | writer?.Dispose(); 47 | 48 | // now we write the V wrapper 49 | writer = new StreamWriter(File.Open(Path.Combine(config.DstDir, config.VWrapperFileName), FileMode.Create)); 50 | writer.WriteLine($"module {config.ModuleName}\n"); 51 | foreach (var file in parsedFiles.Where(f => !config.IsFileExcludedFromVWrapper(f))) 52 | { 53 | foreach (var func in file.ParsedFunctions) 54 | WriteVFunction(writer, func); 55 | } 56 | writer.Dispose(); 57 | } 58 | 59 | static void WriteFile(Config config, ParsedFile file, StreamWriter writer) 60 | { 61 | var module = config.ModuleName; 62 | if (writer == null && config.UseHeaderFolder && config.BaseSourceFolder != file.Folder) 63 | { 64 | module = file.Folder; 65 | var dst = Path.Combine(config.DstDir, file.Folder); 66 | Directory.CreateDirectory(dst); 67 | writer = new StreamWriter(File.Open(Path.Combine(dst, file.Filename + ".v"), FileMode.Create)); 68 | } 69 | else if(writer == null) 70 | { 71 | writer = new StreamWriter(File.Open(Path.Combine(config.DstDir, file.Filename + ".v"), FileMode.Create)); 72 | } 73 | 74 | if (config.SingleVFileExport) 75 | { 76 | writer.WriteLine(); 77 | writer.WriteLine($"// {file.Folder}/{file.Filename}"); 78 | } 79 | else 80 | { 81 | writer.WriteLine($"module {module}"); 82 | } 83 | writer.WriteLine(); 84 | 85 | // write out our types and our C declarations first 86 | foreach (var e in file.Enums) 87 | WriteEnum(writer, e, config); 88 | 89 | foreach (var s in file.Structs) 90 | WriteStruct(writer, s); 91 | 92 | foreach (var f in file.ParsedFunctions) 93 | WriteCFunction(writer, f); 94 | 95 | if (!config.SingleVFileExport) 96 | writer.Dispose(); 97 | } 98 | 99 | static void WriteEnum(StreamWriter writer, CppEnum e, Config config) 100 | { 101 | var hasValue = e.Items.Where(i => i.ValueExpression != null && !string.IsNullOrEmpty(i.ValueExpression.ToString())).Any(); 102 | 103 | var enumItemNames = e.Items.Select(i => i.Name).ToArray(); 104 | if (config.StripEnumItemCommonPrefix && e.Items.Count > 1) 105 | { 106 | string CommonPrefix(string str, params string[] more) 107 | { 108 | var prefixLength = str 109 | .TakeWhile((c, i) => more.All(s => i < s.Length && s[i] == c)) 110 | .Count(); 111 | 112 | return str.Substring(0, prefixLength); 113 | } 114 | 115 | var prefix = CommonPrefix(e.Items[0].Name, e.Items.Select(i => i.Name).Skip(1).ToArray()); 116 | enumItemNames = enumItemNames.Select(n => n.Replace(prefix, "")).ToArray(); 117 | } 118 | 119 | writer.WriteLine($"pub enum {V.GetVEnumName(config.StripFunctionPrefix(e.Name))} {{"); 120 | for (var i = 0; i < e.Items.Count; i++) 121 | { 122 | var item = e.Items[i]; 123 | writer.Write($"\t{V.GetVEnumItemName(enumItemNames[i])}"); 124 | 125 | if (hasValue) 126 | writer.Write($" = {item.Value}"); 127 | writer.WriteLine(); 128 | } 129 | 130 | writer.WriteLine("}"); 131 | writer.WriteLine(); 132 | } 133 | 134 | static void WriteStruct(StreamWriter writer, CppClass s) 135 | { 136 | writer.WriteLine($"pub struct {V.GetVType(s.Name)} {{"); 137 | if (s.Fields.Count > 0) 138 | writer.WriteLine("pub:"); 139 | 140 | foreach (var f in s.Fields) 141 | { 142 | var type = V.GetVType(f.Type); 143 | var name = V.GetCFieldName(f.Name); 144 | writer.WriteLine($"\t{name} {type}"); 145 | } 146 | 147 | writer.WriteLine("}"); 148 | writer.WriteLine(); 149 | } 150 | 151 | static void WriteCFunction(StreamWriter writer, ParsedFunction func) 152 | { 153 | writer.Write($"fn C.{func.Name}("); 154 | foreach (var p in func.Parameters) 155 | { 156 | writer.Write($"{p.Name} {p.VType}"); 157 | if (func.Parameters.Last() != p) 158 | writer.Write(", "); 159 | } 160 | writer.Write(")"); 161 | 162 | if (func.RetType != null) 163 | writer.Write($" {func.VRetType}"); 164 | writer.WriteLine(); 165 | } 166 | 167 | static void WriteVFunction(StreamWriter writer, ParsedFunction func) 168 | { 169 | // first, V function def 170 | writer.WriteLine("[inline]"); 171 | writer.Write($"pub fn {func.VName}("); 172 | foreach (var p in func.Parameters) 173 | { 174 | // special case for byteptr which we convert to string for the function def 175 | if (p.VType == "byteptr") 176 | writer.Write($"{p.VName} string"); 177 | else 178 | writer.Write($"{p.VName} {p.VType}"); 179 | 180 | if (func.Parameters.Last() != p) 181 | writer.Write(", "); 182 | } 183 | writer.Write(")"); 184 | 185 | if (func.VRetType != null) 186 | { 187 | if (func.VRetType == "byteptr") 188 | writer.WriteLine(" string {"); 189 | else 190 | writer.WriteLine($" {func.VRetType} {{"); 191 | } 192 | else 193 | { 194 | writer.WriteLine(" {"); 195 | } 196 | 197 | // now the function body calling the C function 198 | writer.Write("\t"); 199 | if (func.VRetType != null) 200 | { 201 | writer.Write("return "); 202 | 203 | // special case for byteptr, which we cast to string 204 | if (func.VRetType == "byteptr") 205 | writer.Write("string("); 206 | } 207 | 208 | writer.Write($"C.{func.Name}("); 209 | foreach (var p in func.Parameters) 210 | { 211 | // special case for byteptr which was converted to string above 212 | if (p.VType == "byteptr") 213 | writer.Write($"{p.VName}.str"); 214 | else 215 | writer.Write($"{p.VName}"); 216 | 217 | if (func.Parameters.Last() != p) 218 | writer.Write(", "); 219 | } 220 | 221 | // close the string cast if we are returning a byteptr cast to string 222 | if (func.VRetType == "byteptr") 223 | writer.Write(")"); 224 | 225 | writer.WriteLine(")"); 226 | writer.WriteLine("}"); 227 | 228 | writer.WriteLine(); 229 | } 230 | } 231 | } --------------------------------------------------------------------------------