├── .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 | }
--------------------------------------------------------------------------------