You're breathtaking!
This commit is contained in:
606
Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs
Normal file
606
Source/Tools/Flax.Build/Bindings/BindingsGenerator.cs
Normal file
@@ -0,0 +1,606 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Flax.Build.NativeCpp;
|
||||
using BuildData = Flax.Build.Builder.BuildData;
|
||||
|
||||
namespace Flax.Build.Bindings
|
||||
{
|
||||
/// <summary>
|
||||
/// The API bindings generation utility that can produce scripting bindings for another languages to the native code.
|
||||
/// </summary>
|
||||
public static partial class BindingsGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// The bindings generation result.
|
||||
/// </summary>
|
||||
public struct BindingsResult
|
||||
{
|
||||
/// <summary>
|
||||
/// True if module uses bindings, otherwise false.
|
||||
/// </summary>
|
||||
public bool UseBindings;
|
||||
|
||||
/// <summary>
|
||||
/// The generated C++ file path that contains a API bindings wrapper code to setup the glue code.
|
||||
/// </summary>
|
||||
public string GeneratedCppFilePath;
|
||||
|
||||
/// <summary>
|
||||
/// The generated C# file path that contains a API bindings wrapper code to setup the glue code.
|
||||
/// </summary>
|
||||
public string GeneratedCSharpFilePath;
|
||||
}
|
||||
|
||||
public static ModuleInfo ParseModule(BuildData buildData, Module module, BuildOptions moduleOptions = null)
|
||||
{
|
||||
if (buildData.ModulesInfo.TryGetValue(module, out var moduleInfo))
|
||||
return moduleInfo;
|
||||
|
||||
// Try to reuse already parsed bindings info from one of the referenced projects
|
||||
foreach (var referenceBuild in buildData.ReferenceBuilds)
|
||||
{
|
||||
if (referenceBuild.Value.ModulesInfo.TryGetValue(module, out moduleInfo))
|
||||
{
|
||||
buildData.ModulesInfo.Add(module, moduleInfo);
|
||||
return moduleInfo;
|
||||
}
|
||||
}
|
||||
|
||||
using (new ProfileEventScope("ParseModule" + module.Name))
|
||||
{
|
||||
return ParseModuleInner(buildData, module, moduleOptions);
|
||||
}
|
||||
}
|
||||
|
||||
private static ModuleInfo ParseModuleInner(BuildData buildData, Module module, BuildOptions moduleOptions = null)
|
||||
{
|
||||
if (buildData.ModulesInfo.TryGetValue(module, out var moduleInfo))
|
||||
return moduleInfo;
|
||||
|
||||
// Setup bindings module info descriptor
|
||||
moduleInfo = new ModuleInfo
|
||||
{
|
||||
Module = module,
|
||||
Name = module.BinaryModuleName,
|
||||
Namespace = string.Empty,
|
||||
Children = new List<ApiTypeInfo>(),
|
||||
};
|
||||
if (string.IsNullOrEmpty(moduleInfo.Name))
|
||||
throw new Exception("Module name cannot be empty.");
|
||||
buildData.ModulesInfo.Add(module, moduleInfo);
|
||||
|
||||
// Skip for modules that cannot have API bindings
|
||||
if (module is ThirdPartyModule || !module.BuildNativeCode)
|
||||
return moduleInfo;
|
||||
|
||||
if (moduleOptions == null)
|
||||
throw new Exception($"Cannot parse module {module.Name} without options.");
|
||||
|
||||
// Collect all header files that can have public symbols for API
|
||||
var headerFiles = moduleOptions.SourceFiles.Where(x => x.EndsWith(".h")).ToList();
|
||||
|
||||
// Skip if no header files to process
|
||||
if (headerFiles.Count == 0)
|
||||
return moduleInfo;
|
||||
|
||||
// Find and load files with API tags
|
||||
string[] headerFilesContents = null;
|
||||
//using (new ProfileEventScope("LoadHeaderFiles"))
|
||||
{
|
||||
var anyApi = false;
|
||||
for (int i = 0; i < headerFiles.Count; i++)
|
||||
{
|
||||
// Skip scripting types definitions file
|
||||
if (headerFiles[i].Replace('\\', '/').EndsWith("Engine/Core/Config.h", StringComparison.Ordinal) ||
|
||||
headerFiles[i].EndsWith("EditorContextAPI.h", StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
// Check if file contains any valid API tag
|
||||
var contents = File.ReadAllText(headerFiles[i]);
|
||||
for (int j = 0; j < ApiTokens.SearchTags.Length; j++)
|
||||
{
|
||||
if (contents.Contains(ApiTokens.SearchTags[j]))
|
||||
{
|
||||
if (headerFilesContents == null)
|
||||
headerFilesContents = new string[headerFiles.Count];
|
||||
headerFilesContents[i] = contents;
|
||||
anyApi = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyApi)
|
||||
return moduleInfo;
|
||||
}
|
||||
|
||||
// Skip if none of the headers was modified after last time generated C++ file was edited
|
||||
// TODO: skip parsing if module has API cache file -> then load it from disk
|
||||
/*if (!forceGenerate)
|
||||
{
|
||||
var lastGenerateTime = File.GetLastWriteTime(bindings.GeneratedCppFilePath);
|
||||
var anyModified = false;
|
||||
for (int i = 0; i < headerFiles.Count; i++)
|
||||
{
|
||||
if (File.GetLastWriteTime(headerFiles[i]) > lastGenerateTime)
|
||||
{
|
||||
anyModified = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyModified)
|
||||
return;
|
||||
}*/
|
||||
|
||||
Log.Verbose($"Parsing API bindings for {module.Name} ({moduleInfo.Name})");
|
||||
|
||||
// Process all header files to generate the module API code reflection
|
||||
var context = new ParsingContext
|
||||
{
|
||||
CurrentAccessLevel = AccessLevel.Public,
|
||||
ScopeTypeStack = new Stack<ApiTypeInfo>(),
|
||||
ScopeAccessStack = new Stack<AccessLevel>(),
|
||||
PreprocessorDefines = new Dictionary<string, string>(),
|
||||
};
|
||||
for (int i = 0; i < headerFiles.Count; i++)
|
||||
{
|
||||
if (headerFilesContents[i] == null)
|
||||
continue;
|
||||
var fileInfo = new FileInfo
|
||||
{
|
||||
Parent = null,
|
||||
Children = new List<ApiTypeInfo>(),
|
||||
Name = headerFiles[i],
|
||||
Namespace = moduleInfo.Name,
|
||||
};
|
||||
moduleInfo.AddChild(fileInfo);
|
||||
|
||||
try
|
||||
{
|
||||
// Tokenize the source
|
||||
var tokenizer = new Tokenizer();
|
||||
tokenizer.Tokenize(headerFilesContents[i]);
|
||||
|
||||
// Init the context
|
||||
context.Tokenizer = tokenizer;
|
||||
context.File = fileInfo;
|
||||
context.ScopeInfo = null;
|
||||
context.ScopeTypeStack.Clear();
|
||||
context.ScopeAccessStack.Clear();
|
||||
context.PreprocessorDefines.Clear();
|
||||
context.EnterScope(fileInfo);
|
||||
|
||||
// Process the source code
|
||||
ApiTypeInfo scopeType = null;
|
||||
Token prevToken = null;
|
||||
while (true)
|
||||
{
|
||||
// Move to the next token
|
||||
var token = tokenizer.NextToken();
|
||||
if (token == null)
|
||||
continue;
|
||||
if (token.Type == TokenType.EndOfFile)
|
||||
break;
|
||||
|
||||
// Parse API_.. tags in source code
|
||||
if (token.Type == TokenType.Identifier && token.Value.StartsWith("API_", StringComparison.Ordinal))
|
||||
{
|
||||
if (string.Equals(token.Value, ApiTokens.Class, StringComparison.Ordinal))
|
||||
{
|
||||
if (!(context.ScopeInfo is FileInfo))
|
||||
throw new NotImplementedException("TODO: add support for nested classes in scripting API");
|
||||
|
||||
var classInfo = ParseClass(ref context);
|
||||
scopeType = classInfo;
|
||||
context.ScopeInfo.AddChild(scopeType);
|
||||
context.CurrentAccessLevel = AccessLevel.Public;
|
||||
}
|
||||
else if (string.Equals(token.Value, ApiTokens.Property, StringComparison.Ordinal))
|
||||
{
|
||||
var propertyInfo = ParseProperty(ref context);
|
||||
}
|
||||
else if (string.Equals(token.Value, ApiTokens.Function, StringComparison.Ordinal))
|
||||
{
|
||||
var functionInfo = ParseFunction(ref context);
|
||||
|
||||
if (context.ScopeInfo is ClassInfo classInfo)
|
||||
classInfo.Functions.Add(functionInfo);
|
||||
else if (context.ScopeInfo is StructureInfo structureInfo)
|
||||
structureInfo.Functions.Add(functionInfo);
|
||||
else
|
||||
throw new Exception($"Not supported free-function {functionInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class to use API bindings for it.");
|
||||
}
|
||||
else if (string.Equals(token.Value, ApiTokens.Enum, StringComparison.Ordinal))
|
||||
{
|
||||
var enumInfo = ParseEnum(ref context);
|
||||
context.ScopeInfo.AddChild(enumInfo);
|
||||
}
|
||||
else if (string.Equals(token.Value, ApiTokens.Struct, StringComparison.Ordinal))
|
||||
{
|
||||
var structureInfo = ParseStructure(ref context);
|
||||
scopeType = structureInfo;
|
||||
context.ScopeInfo.AddChild(scopeType);
|
||||
context.CurrentAccessLevel = AccessLevel.Public;
|
||||
}
|
||||
else if (string.Equals(token.Value, ApiTokens.Field, StringComparison.Ordinal))
|
||||
{
|
||||
var fieldInfo = ParseField(ref context);
|
||||
var scopeInfo = context.ValidScopeInfoFromStack;
|
||||
|
||||
if (scopeInfo is ClassInfo classInfo)
|
||||
classInfo.Fields.Add(fieldInfo);
|
||||
else if (scopeInfo is StructureInfo structureInfo)
|
||||
structureInfo.Fields.Add(fieldInfo);
|
||||
else
|
||||
throw new Exception($"Not supported location for field {fieldInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class or structure to use API bindings for it.");
|
||||
}
|
||||
else if (string.Equals(token.Value, ApiTokens.Event, StringComparison.Ordinal))
|
||||
{
|
||||
var eventInfo = ParseEvent(ref context);
|
||||
var scopeInfo = context.ValidScopeInfoFromStack;
|
||||
|
||||
if (scopeInfo is ClassInfo classInfo)
|
||||
classInfo.Events.Add(eventInfo);
|
||||
else
|
||||
throw new Exception($"Not supported location for event {eventInfo.Name} at line {tokenizer.CurrentLine}. Place it in the class to use API bindings for it.");
|
||||
}
|
||||
else if (string.Equals(token.Value, ApiTokens.InjectCppCode, StringComparison.Ordinal))
|
||||
{
|
||||
var injectCppCodeInfo = ParseInjectCppCode(ref context);
|
||||
fileInfo.AddChild(injectCppCodeInfo);
|
||||
}
|
||||
else if (string.Equals(token.Value, ApiTokens.AutoSerialization, StringComparison.Ordinal))
|
||||
{
|
||||
if (context.ScopeInfo is ClassInfo classInfo)
|
||||
classInfo.IsAutoSerialization = true;
|
||||
else if (context.ScopeInfo is StructureInfo structureInfo)
|
||||
structureInfo.IsAutoSerialization = true;
|
||||
else
|
||||
throw new Exception($"Not supported location for {ApiTokens.AutoSerialization} at line {tokenizer.CurrentLine}. Place it in the class or structure that uses API bindings.");
|
||||
}
|
||||
}
|
||||
|
||||
// Track access level inside class
|
||||
if (context.ScopeInfo != null && token.Type == TokenType.Colon && prevToken != null && prevToken.Type == TokenType.Identifier)
|
||||
{
|
||||
if (string.Equals(prevToken.Value, "public", StringComparison.Ordinal))
|
||||
{
|
||||
context.CurrentAccessLevel = AccessLevel.Public;
|
||||
}
|
||||
else if (string.Equals(prevToken.Value, "protected", StringComparison.Ordinal))
|
||||
{
|
||||
context.CurrentAccessLevel = AccessLevel.Protected;
|
||||
}
|
||||
else if (string.Equals(prevToken.Value, "private", StringComparison.Ordinal))
|
||||
{
|
||||
context.CurrentAccessLevel = AccessLevel.Private;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle preprocessor blocks
|
||||
if (token.Type == TokenType.Preprocessor)
|
||||
{
|
||||
token = tokenizer.NextToken();
|
||||
switch (token.Value)
|
||||
{
|
||||
case "define":
|
||||
{
|
||||
token = tokenizer.NextToken();
|
||||
var name = token.Value;
|
||||
var value = string.Empty;
|
||||
token = tokenizer.NextToken(true);
|
||||
while (token.Type != TokenType.Newline)
|
||||
{
|
||||
value += token.Value;
|
||||
token = tokenizer.NextToken(true);
|
||||
}
|
||||
value = value.Trim();
|
||||
context.PreprocessorDefines[name] = value;
|
||||
break;
|
||||
}
|
||||
case "if":
|
||||
{
|
||||
// Parse condition
|
||||
var condition = string.Empty;
|
||||
token = tokenizer.NextToken(true);
|
||||
while (token.Type != TokenType.Newline)
|
||||
{
|
||||
condition += token.Value;
|
||||
token = tokenizer.NextToken(true);
|
||||
}
|
||||
|
||||
// Replace contents with defines
|
||||
condition = condition.Trim();
|
||||
condition = ReplacePreProcessorDefines(condition, context.PreprocessorDefines.Keys);
|
||||
condition = ReplacePreProcessorDefines(condition, moduleOptions.PublicDefinitions);
|
||||
condition = ReplacePreProcessorDefines(condition, moduleOptions.PrivateDefinitions);
|
||||
condition = ReplacePreProcessorDefines(condition, moduleOptions.CompileEnv.PreprocessorDefinitions);
|
||||
condition = condition.Replace("false", "0");
|
||||
condition = condition.Replace("true", "1");
|
||||
|
||||
// Check condition
|
||||
// TODO: support expressions in preprocessor defines in API headers?
|
||||
if (condition != "1")
|
||||
{
|
||||
// Skip chunk of code
|
||||
ParsePreprocessorIf(fileInfo, tokenizer, ref token);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "ifdef":
|
||||
{
|
||||
// Parse condition
|
||||
var define = string.Empty;
|
||||
token = tokenizer.NextToken(true);
|
||||
while (token.Type != TokenType.Newline)
|
||||
{
|
||||
define += token.Value;
|
||||
token = tokenizer.NextToken(true);
|
||||
}
|
||||
|
||||
// Check condition
|
||||
define = define.Trim();
|
||||
if (!context.PreprocessorDefines.ContainsKey(define) && !moduleOptions.CompileEnv.PreprocessorDefinitions.Contains(define))
|
||||
{
|
||||
ParsePreprocessorIf(fileInfo, tokenizer, ref token);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scope tracking
|
||||
if (token.Type == TokenType.LeftCurlyBrace)
|
||||
{
|
||||
context.ScopeTypeStack.Push(scopeType);
|
||||
context.ScopeInfo = context.ScopeTypeStack.Peek();
|
||||
scopeType = null;
|
||||
}
|
||||
else if (token.Type == TokenType.RightCurlyBrace)
|
||||
{
|
||||
context.ScopeTypeStack.Pop();
|
||||
if (context.ScopeTypeStack.Count == 0)
|
||||
throw new Exception($"Mismatch of the {{}} braces pair in file '{fileInfo.Name}' at line {tokenizer.CurrentLine}.");
|
||||
context.ScopeInfo = context.ScopeTypeStack.Peek();
|
||||
if (context.ScopeInfo is FileInfo)
|
||||
context.CurrentAccessLevel = AccessLevel.Public;
|
||||
}
|
||||
|
||||
prevToken = token;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Failed to parse '{fileInfo.Name}' file to generate bindings.");
|
||||
Log.Exception(ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize API
|
||||
moduleInfo.Init(buildData);
|
||||
|
||||
return moduleInfo;
|
||||
}
|
||||
|
||||
private static string ReplacePreProcessorDefines(string text, IEnumerable<string> defines)
|
||||
{
|
||||
foreach (var define in defines)
|
||||
{
|
||||
if (text.Contains(define))
|
||||
text = text.Replace(define, "1");
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
private static void ParsePreprocessorIf(FileInfo fileInfo, Tokenizer tokenizer, ref Token token)
|
||||
{
|
||||
int ifsCount = 1;
|
||||
while (token.Type != TokenType.EndOfFile && ifsCount != 0)
|
||||
{
|
||||
token = tokenizer.NextToken();
|
||||
if (token.Type == TokenType.Preprocessor)
|
||||
{
|
||||
token = tokenizer.NextToken();
|
||||
switch (token.Value)
|
||||
{
|
||||
case "if":
|
||||
case "ifdef":
|
||||
ifsCount++;
|
||||
break;
|
||||
case "endif":
|
||||
ifsCount--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ifsCount != 0)
|
||||
throw new Exception($"Invalid #if/endif pairing in file '{fileInfo.Name}'. Failed to generate API bindings for it.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The API bindings generation utility that can produce scripting bindings for another languages to the native code.
|
||||
/// </summary>
|
||||
public static void GenerateBindings(BuildData buildData, Module module, ref BuildOptions moduleOptions, out BindingsResult bindings)
|
||||
{
|
||||
// Parse module (or load from cache)
|
||||
bindings = new BindingsResult
|
||||
{
|
||||
GeneratedCppFilePath = Path.Combine(moduleOptions.IntermediateFolder, module.Name + ".Bindings.Gen.cpp"),
|
||||
GeneratedCSharpFilePath = Path.Combine(moduleOptions.IntermediateFolder, module.Name + ".Bindings.Gen.cs"),
|
||||
};
|
||||
var moduleInfo = ParseModule(buildData, module, moduleOptions);
|
||||
|
||||
// Process parsed API
|
||||
foreach (var child in moduleInfo.Children)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var apiTypeInfo in child.Children)
|
||||
ProcessAndValidate(buildData, apiTypeInfo);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (child is FileInfo fileInfo)
|
||||
Log.Error($"Failed to validate '{fileInfo.Name}' file to generate bindings.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate bindings for scripting
|
||||
Log.Verbose($"Generating API bindings for {module.Name} ({moduleInfo.Name})");
|
||||
GenerateCpp(buildData, moduleInfo, ref bindings);
|
||||
GenerateCSharp(buildData, moduleInfo, ref bindings);
|
||||
|
||||
// TODO: add support for extending this code and support generating bindings for other scripting languages
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The API bindings generation utility that can produce scripting bindings for another languages to the native code.
|
||||
/// </summary>
|
||||
public static void GenerateBindings(BuildData buildData)
|
||||
{
|
||||
foreach (var binaryModule in buildData.BinaryModules)
|
||||
{
|
||||
using (new ProfileEventScope(binaryModule.Key))
|
||||
{
|
||||
// Generate bindings for binary module
|
||||
Log.Verbose($"Generating binary module bindings for {binaryModule.Key}");
|
||||
GenerateCpp(buildData, binaryModule);
|
||||
GenerateCSharp(buildData, binaryModule);
|
||||
|
||||
// TODO: add support for extending this code and support generating bindings for other scripting languages
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessAndValidate(BuildData buildData, ApiTypeInfo apiTypeInfo)
|
||||
{
|
||||
if (apiTypeInfo is ClassInfo classInfo)
|
||||
ProcessAndValidate(buildData, classInfo);
|
||||
else if (apiTypeInfo is StructureInfo structureInfo)
|
||||
ProcessAndValidate(buildData, structureInfo);
|
||||
|
||||
foreach (var child in apiTypeInfo.Children)
|
||||
ProcessAndValidate(buildData, child);
|
||||
}
|
||||
|
||||
private static void ProcessAndValidate(BuildData buildData, ClassInfo classInfo)
|
||||
{
|
||||
if (classInfo.UniqueFunctionNames == null)
|
||||
classInfo.UniqueFunctionNames = new HashSet<string>();
|
||||
|
||||
foreach (var fieldInfo in classInfo.Fields)
|
||||
{
|
||||
if (fieldInfo.Access == AccessLevel.Private)
|
||||
continue;
|
||||
|
||||
fieldInfo.Getter = new FunctionInfo
|
||||
{
|
||||
Name = "Get" + fieldInfo.Name,
|
||||
Comment = fieldInfo.Comment,
|
||||
IsStatic = fieldInfo.IsStatic,
|
||||
Access = fieldInfo.Access,
|
||||
Attributes = fieldInfo.Attributes,
|
||||
ReturnType = fieldInfo.Type,
|
||||
Parameters = new List<FunctionInfo.ParameterInfo>(),
|
||||
IsVirtual = false,
|
||||
IsConst = true,
|
||||
Glue = new FunctionInfo.GlueInfo()
|
||||
};
|
||||
ProcessAndValidate(classInfo, fieldInfo.Getter);
|
||||
fieldInfo.Getter.Name = fieldInfo.Name;
|
||||
|
||||
if (!fieldInfo.IsReadOnly)
|
||||
{
|
||||
if (fieldInfo.Type.IsArray)
|
||||
throw new NotImplementedException("Use ReadOnly on field. TODO: add support for setter for fixed-array fields.");
|
||||
|
||||
fieldInfo.Setter = new FunctionInfo
|
||||
{
|
||||
Name = "Set" + fieldInfo.Name,
|
||||
Comment = fieldInfo.Comment,
|
||||
IsStatic = fieldInfo.IsStatic,
|
||||
Access = fieldInfo.Access,
|
||||
Attributes = fieldInfo.Attributes,
|
||||
ReturnType = new TypeInfo
|
||||
{
|
||||
Type = "void",
|
||||
},
|
||||
Parameters = new List<FunctionInfo.ParameterInfo>
|
||||
{
|
||||
new FunctionInfo.ParameterInfo
|
||||
{
|
||||
Name = "value",
|
||||
Type = fieldInfo.Type,
|
||||
},
|
||||
},
|
||||
IsVirtual = false,
|
||||
IsConst = true,
|
||||
Glue = new FunctionInfo.GlueInfo()
|
||||
};
|
||||
ProcessAndValidate(classInfo, fieldInfo.Setter);
|
||||
fieldInfo.Setter.Name = fieldInfo.Name;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var propertyInfo in classInfo.Properties)
|
||||
{
|
||||
if (propertyInfo.Getter != null)
|
||||
ProcessAndValidate(classInfo, propertyInfo.Getter);
|
||||
if (propertyInfo.Setter != null)
|
||||
ProcessAndValidate(classInfo, propertyInfo.Setter);
|
||||
}
|
||||
|
||||
foreach (var functionInfo in classInfo.Functions)
|
||||
ProcessAndValidate(classInfo, functionInfo);
|
||||
}
|
||||
|
||||
private static void ProcessAndValidate(BuildData buildData, StructureInfo structureInfo)
|
||||
{
|
||||
foreach (var fieldInfo in structureInfo.Fields)
|
||||
{
|
||||
// TODO: support bit-fields in structure fields
|
||||
if (fieldInfo.Type.IsBitField)
|
||||
throw new NotImplementedException($"TODO: support bit-fields in structure fields (found field {fieldInfo} in structure {structureInfo.Name})");
|
||||
|
||||
// Pointers are fine
|
||||
if (fieldInfo.Type.IsPtr)
|
||||
continue;
|
||||
|
||||
// In-build types
|
||||
if (CSharpNativeToManagedBasicTypes.ContainsKey(fieldInfo.Type.Type))
|
||||
continue;
|
||||
if (CSharpNativeToManagedDefault.ContainsKey(fieldInfo.Type.Type))
|
||||
continue;
|
||||
|
||||
// Find API type info for this field type
|
||||
var apiType = FindApiTypeInfo(buildData, fieldInfo.Type, structureInfo);
|
||||
if (apiType != null)
|
||||
continue;
|
||||
|
||||
throw new Exception($"Unknown field type '{fieldInfo.Type} {fieldInfo.Name}' in structure '{structureInfo.Name}'.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessAndValidate(ClassInfo classInfo, FunctionInfo functionInfo)
|
||||
{
|
||||
// Ensure that methods have unique names for bindings
|
||||
if (classInfo.UniqueFunctionNames == null)
|
||||
classInfo.UniqueFunctionNames = new HashSet<string>();
|
||||
int idx = 1;
|
||||
functionInfo.UniqueName = functionInfo.Name;
|
||||
while (classInfo.UniqueFunctionNames.Contains(functionInfo.UniqueName))
|
||||
functionInfo.UniqueName = functionInfo.Name + idx++;
|
||||
classInfo.UniqueFunctionNames.Add(functionInfo.UniqueName);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user