Add support for multi-threaded scripting API headers parsing
This commit is contained in:
@@ -17,6 +17,7 @@ namespace Flax.Build.Bindings
|
||||
public Stack<ApiTypeInfo> ScopeTypeStack;
|
||||
public Stack<AccessLevel> ScopeAccessStack;
|
||||
public Dictionary<string, string> PreprocessorDefines;
|
||||
public List<string> StringCache;
|
||||
|
||||
public ApiTypeInfo ValidScopeInfoFromStack
|
||||
{
|
||||
@@ -46,14 +47,12 @@ namespace Flax.Build.Bindings
|
||||
}
|
||||
}
|
||||
|
||||
private static List<string> _commentCache;
|
||||
|
||||
private static string[] ParseComment(ref ParsingContext context)
|
||||
{
|
||||
if (_commentCache == null)
|
||||
_commentCache = new List<string>();
|
||||
if (context.StringCache == null)
|
||||
context.StringCache = new List<string>();
|
||||
else
|
||||
_commentCache.Clear();
|
||||
context.StringCache.Clear();
|
||||
|
||||
int tokensCount = 0;
|
||||
bool isValid = true;
|
||||
@@ -77,7 +76,7 @@ namespace Flax.Build.Bindings
|
||||
if (commentLine.StartsWith("// "))
|
||||
commentLine = "/// " + commentLine.Substring(3);
|
||||
|
||||
_commentCache.Insert(0, commentLine);
|
||||
context.StringCache.Insert(0, commentLine);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -90,14 +89,14 @@ namespace Flax.Build.Bindings
|
||||
for (var i = 0; i < tokensCount; i++)
|
||||
context.Tokenizer.NextToken(true, true);
|
||||
|
||||
if (_commentCache.Count == 1)
|
||||
if (context.StringCache.Count == 1)
|
||||
{
|
||||
// Ensure to have summary begin/end pair
|
||||
_commentCache.Insert(0, "/// <summary>");
|
||||
_commentCache.Add("/// </summary>");
|
||||
context.StringCache.Insert(0, "/// <summary>");
|
||||
context.StringCache.Add("/// </summary>");
|
||||
}
|
||||
|
||||
return _commentCache.ToArray();
|
||||
return context.StringCache.ToArray();
|
||||
}
|
||||
|
||||
private struct TagParameter
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Flax.Build.NativeCpp;
|
||||
using BuildData = Flax.Build.Builder.BuildData;
|
||||
|
||||
@@ -98,324 +100,327 @@ namespace Flax.Build.Bindings
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: use async tasks or thread pool to load and parse multiple header files at once using multi-threading
|
||||
|
||||
// Find and load files with API tags
|
||||
string[] headerFilesContents = null;
|
||||
using (new ProfileEventScope("LoadHeaderFiles"))
|
||||
// Parse bindings
|
||||
Log.Verbose($"Parsing API bindings for {module.Name} ({moduleInfo.Name})");
|
||||
int concurrency = Math.Min(Math.Max(1, (int)(Environment.ProcessorCount * Configuration.ConcurrencyProcessorScale)), Configuration.MaxConcurrency);
|
||||
concurrency = 1; // Disable concurrency for parsing (the gain is unnoticeable or even worse in some cases)
|
||||
if (concurrency == 1 || headerFiles.Count < 2 * concurrency)
|
||||
{
|
||||
// Single-threaded
|
||||
for (int i = 0; i < headerFiles.Count; i++)
|
||||
{
|
||||
var contents = File.ReadAllText(headerFiles[i]);
|
||||
for (int j = 0; j < ApiTokens.SearchTags.Length; j++)
|
||||
using (new ProfileEventScope(Path.GetFileName(headerFiles[i])))
|
||||
{
|
||||
if (contents.Contains(ApiTokens.SearchTags[j]))
|
||||
{
|
||||
if (headerFilesContents == null)
|
||||
headerFilesContents = new string[headerFiles.Count];
|
||||
headerFilesContents[i] = contents;
|
||||
break;
|
||||
}
|
||||
ParseModuleInnerAsync(moduleInfo, moduleOptions, headerFiles, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (headerFilesContents == null)
|
||||
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)
|
||||
else
|
||||
{
|
||||
var lastGenerateTime = File.GetLastWriteTime(bindings.GeneratedCppFilePath);
|
||||
var anyModified = false;
|
||||
for (int i = 0; i < headerFiles.Count; i++)
|
||||
// Sort by file size to improve performance (parse larger files first)
|
||||
headerFiles.Sort((a, b) => new System.IO.FileInfo(b).Length.CompareTo(new System.IO.FileInfo(a).Length));
|
||||
|
||||
// Multi-threaded
|
||||
ThreadPool.GetMinThreads(out var workerThreads, out var completionPortThreads);
|
||||
if (workerThreads != concurrency)
|
||||
ThreadPool.SetMaxThreads(concurrency, completionPortThreads);
|
||||
Parallel.For(0, headerFiles.Count, (i, state) =>
|
||||
{
|
||||
if (File.GetLastWriteTime(headerFiles[i]) > lastGenerateTime)
|
||||
using (new ProfileEventScope(Path.GetFileName(headerFiles[i])))
|
||||
{
|
||||
anyModified = true;
|
||||
break;
|
||||
ParseModuleInnerAsync(moduleInfo, moduleOptions, headerFiles, i);
|
||||
}
|
||||
}
|
||||
|
||||
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.Interface, StringComparison.Ordinal))
|
||||
{
|
||||
if (!(context.ScopeInfo is FileInfo))
|
||||
throw new NotImplementedException("TODO: add support for nested interfaces in scripting API");
|
||||
|
||||
var interfaceInfo = ParseInterface(ref context);
|
||||
scopeType = interfaceInfo;
|
||||
context.ScopeInfo.AddChild(scopeType);
|
||||
context.CurrentAccessLevel = AccessLevel.Public;
|
||||
}
|
||||
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)
|
||||
{
|
||||
var tokenValue = token.Value.Trim();
|
||||
if (tokenValue.Length == 0)
|
||||
{
|
||||
token = tokenizer.NextToken(true);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Very simple defines processing
|
||||
tokenValue = ReplacePreProcessorDefines(tokenValue, context.PreprocessorDefines.Keys);
|
||||
tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.PublicDefinitions);
|
||||
tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.PrivateDefinitions);
|
||||
tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.CompileEnv.PreprocessorDefinitions);
|
||||
tokenValue = tokenValue.Replace("false", "0");
|
||||
tokenValue = tokenValue.Replace("true", "1");
|
||||
tokenValue = tokenValue.Replace("||", "|");
|
||||
if (tokenValue.Length != 0 && tokenValue != "1" && tokenValue != "0" && tokenValue != "|")
|
||||
tokenValue = "0";
|
||||
|
||||
condition += tokenValue;
|
||||
token = tokenizer.NextToken(true);
|
||||
}
|
||||
|
||||
// Filter condition
|
||||
condition = condition.Replace("1|1", "1");
|
||||
condition = condition.Replace("1|0", "1");
|
||||
condition = condition.Replace("0|1", "1");
|
||||
|
||||
// Skip chunk of code of condition fails
|
||||
if (condition != "1")
|
||||
{
|
||||
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);
|
||||
using (new ProfileEventScope("Init"))
|
||||
{
|
||||
moduleInfo.Init(buildData);
|
||||
}
|
||||
|
||||
return moduleInfo;
|
||||
}
|
||||
|
||||
private static void ParseModuleInnerAsync(ModuleInfo moduleInfo, BuildOptions moduleOptions, List<string> headerFiles, int workIndex)
|
||||
{
|
||||
// Find and load files with API tags
|
||||
bool hasApi = false;
|
||||
string headerFileContents = File.ReadAllText(headerFiles[workIndex]);
|
||||
for (int j = 0; j < ApiTokens.SearchTags.Length; j++)
|
||||
{
|
||||
if (headerFileContents.Contains(ApiTokens.SearchTags[j]))
|
||||
{
|
||||
hasApi = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasApi)
|
||||
return;
|
||||
|
||||
// Process header file to generate the module API code reflection
|
||||
var fileInfo = new FileInfo
|
||||
{
|
||||
Parent = null,
|
||||
Children = new List<ApiTypeInfo>(),
|
||||
Name = headerFiles[workIndex],
|
||||
Namespace = moduleInfo.Name,
|
||||
};
|
||||
lock (moduleInfo)
|
||||
{
|
||||
moduleInfo.AddChild(fileInfo);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Tokenize the source
|
||||
var tokenizer = new Tokenizer();
|
||||
tokenizer.Tokenize(headerFileContents);
|
||||
|
||||
// Init the context
|
||||
var context = new ParsingContext
|
||||
{
|
||||
File = fileInfo,
|
||||
Tokenizer = tokenizer,
|
||||
ScopeInfo = null,
|
||||
CurrentAccessLevel = AccessLevel.Public,
|
||||
ScopeTypeStack = new Stack<ApiTypeInfo>(),
|
||||
ScopeAccessStack = new Stack<AccessLevel>(),
|
||||
PreprocessorDefines = new Dictionary<string, string>(),
|
||||
};
|
||||
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.Interface, StringComparison.Ordinal))
|
||||
{
|
||||
if (!(context.ScopeInfo is FileInfo))
|
||||
throw new NotImplementedException("TODO: add support for nested interfaces in scripting API");
|
||||
|
||||
var interfaceInfo = ParseInterface(ref context);
|
||||
scopeType = interfaceInfo;
|
||||
context.ScopeInfo.AddChild(scopeType);
|
||||
context.CurrentAccessLevel = AccessLevel.Public;
|
||||
}
|
||||
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)
|
||||
{
|
||||
var tokenValue = token.Value.Trim();
|
||||
if (tokenValue.Length == 0)
|
||||
{
|
||||
token = tokenizer.NextToken(true);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Very simple defines processing
|
||||
tokenValue = ReplacePreProcessorDefines(tokenValue, context.PreprocessorDefines.Keys);
|
||||
tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.PublicDefinitions);
|
||||
tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.PrivateDefinitions);
|
||||
tokenValue = ReplacePreProcessorDefines(tokenValue, moduleOptions.CompileEnv.PreprocessorDefinitions);
|
||||
tokenValue = tokenValue.Replace("false", "0");
|
||||
tokenValue = tokenValue.Replace("true", "1");
|
||||
tokenValue = tokenValue.Replace("||", "|");
|
||||
if (tokenValue.Length != 0 && tokenValue != "1" && tokenValue != "0" && tokenValue != "|")
|
||||
tokenValue = "0";
|
||||
|
||||
condition += tokenValue;
|
||||
token = tokenizer.NextToken(true);
|
||||
}
|
||||
|
||||
// Filter condition
|
||||
condition = condition.Replace("1|1", "1");
|
||||
condition = condition.Replace("1|0", "1");
|
||||
condition = condition.Replace("0|1", "1");
|
||||
|
||||
// Skip chunk of code of condition fails
|
||||
if (condition != "1")
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private static string ReplacePreProcessorDefines(string text, IEnumerable<string> defines)
|
||||
{
|
||||
foreach (var define in defines)
|
||||
@@ -594,7 +599,6 @@ namespace Flax.Build.Bindings
|
||||
{
|
||||
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})");
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Flax.Build.Bindings
|
||||
/// <summary>
|
||||
/// The native file information for bindings generator.
|
||||
/// </summary>
|
||||
public class FileInfo : ApiTypeInfo, IComparable<FileInfo>
|
||||
public class FileInfo : ApiTypeInfo, IComparable, IComparable<FileInfo>
|
||||
{
|
||||
public override void AddChild(ApiTypeInfo apiTypeInfo)
|
||||
{
|
||||
@@ -26,5 +26,12 @@ namespace Flax.Build.Bindings
|
||||
{
|
||||
return System.IO.Path.GetFileName(Name);
|
||||
}
|
||||
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (obj is ApiTypeInfo apiTypeInfo)
|
||||
return Name.CompareTo(apiTypeInfo.Name);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,5 +13,14 @@ namespace Flax.Build.Bindings
|
||||
{
|
||||
return "module " + Name;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Init(Builder.BuildData buildData)
|
||||
{
|
||||
base.Init(buildData);
|
||||
|
||||
// Sort module files to prevent bindings rebuild due to order changes (list might be created in async)
|
||||
Children.Sort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user