예제 #1
0
        private static void ProcessAndValidate(BuildData buildData, StructureInfo structureInfo)
        {
            foreach (var fieldInfo in structureInfo.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}'.");
            }
        }
예제 #2
0
        /// <summary>
        /// Finds the API type information.
        /// </summary>
        /// <param name="buildData">The build data.</param>
        /// <param name="typeInfo">The type information.</param>
        /// <param name="caller">The calling type. It's parent module types and references are used to find the given API type.</param>
        /// <returns>The found API type inf oor null.</returns>
        public static ApiTypeInfo FindApiTypeInfo(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller)
        {
            if (typeInfo == null)
            {
                return(null);
            }
            var result = FindApiTypeInfoInner(typeInfo, caller);

            if (result != null)
            {
                return(result);
            }
            if (buildData.TypeCache.TryGetValue(typeInfo, out result))
            {
                return(result);
            }

            // Find across in-build types
            if (InBuildTypes.TryGetValue(typeInfo, out result))
            {
                return(result);
            }

            // Find across all loaded modules for this build
            foreach (var e in buildData.ModulesInfo)
            {
                result = FindApiTypeInfoInner(typeInfo, e.Value);
                if (result != null)
                {
                    buildData.TypeCache.Add(typeInfo, result);
                    return(result);
                }
            }

            // Check if using nested typename
            if (typeInfo.Type.Contains("::"))
            {
                var nesting = typeInfo.Type.Split(new[] { "::" }, StringSplitOptions.None);
                result = FindApiTypeInfo(buildData, new TypeInfo {
                    Type = nesting[0],
                }, caller);
                for (int i = 1; i < nesting.Length; i++)
                {
                    if (result == null)
                    {
                        return(null);
                    }
                    result = FindApiTypeInfoInner(new TypeInfo {
                        Type = nesting[i],
                    }, result);
                }
                return(result);
            }

            //buildData.TypeCache.Add(typeInfo, null);
            //Log.Warning("Unknown API type: " + typeInfo);
            return(null);
        }
예제 #3
0
        /// <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)
            var moduleInfo = ParseModule(buildData, module, moduleOptions);

            bindings = new BindingsResult
            {
                UseBindings             = UseBindings(moduleInfo),
                GeneratedCppFilePath    = Path.Combine(moduleOptions.IntermediateFolder, module.Name + ".Bindings.Gen.cpp"),
                GeneratedCSharpFilePath = Path.Combine(moduleOptions.IntermediateFolder, module.Name + ".Bindings.Gen.cs"),
            };

            if (bindings.UseBindings)
            {
                buildData.Modules.TryGetValue(moduleInfo.Module, out var moduleBuildInfo);

                // Ensure that generated files are included into build
                if (EngineConfiguration.WithCSharp(buildData.TargetOptions) && !moduleBuildInfo.SourceFiles.Contains(bindings.GeneratedCSharpFilePath))
                {
                    moduleBuildInfo.SourceFiles.Add(bindings.GeneratedCSharpFilePath);
                }
            }

            // Skip if module is cached (no scripting API changed)
            if (moduleInfo.IsFromCache)
            {
                return;
            }

            // Initialize parsed API
            using (new ProfileEventScope("Init"))
            {
                moduleInfo.Init(buildData);
            }

            // Generate bindings for scripting
            if (bindings.UseBindings)
            {
                Log.Verbose($"Generating API bindings for {module.Name} ({moduleInfo.Name})");
                using (new ProfileEventScope("Cpp"))
                    GenerateCpp(buildData, moduleInfo, ref bindings);
                if (EngineConfiguration.WithCSharp(buildData.TargetOptions))
                {
                    using (new ProfileEventScope("CSharp"))
                        GenerateCSharp(buildData, moduleInfo, ref bindings);
                }
                GenerateBinaryModuleBindings?.Invoke(buildData, moduleInfo, ref bindings);
            }
        }
예제 #4
0
        /// <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
                }
            }
        }
예제 #5
0
        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);
            }
        }
예제 #6
0
 /// <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);
             if (EngineConfiguration.WithCSharp(buildData.TargetOptions))
             {
                 GenerateCSharp(buildData, binaryModule);
             }
             GenerateModuleBindings?.Invoke(buildData, binaryModule);
         }
     }
 }
        private static ApiTypeInfo FindApiTypeInfoInner(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo parent)
        {
            ApiTypeInfo result = null;

            foreach (var child in parent.Children)
            {
                if (child.Name == typeInfo.Type)
                {
                    result = child;

                    // Special case when searching for template types (use instantiated template if input type has generic arguments)
                    if (child is ClassStructInfo classStructInfo && classStructInfo.IsTemplate && typeInfo.GenericArgs != null)
                    {
                        // Support instantiated template type with instantiated arguments (eg. 'typedef Vector3Base<Real> Vector3' where 'typedef float Real')
                        var inflatedType = typeInfo.Inflate(buildData, child);

                        // Find any typedef which introduced this type (eg. 'typedef Vector3Base<float> Float3')
                        result = FindTypedef(buildData, inflatedType, parent.ParentModule);
                        if (result == TypedefInfo.Current)
                        {
                            result = child; // Use template type for the current typedef
                        }
                        else if (result == null)
                        {
                            continue; // Invalid template match
                        }
                    }

                    result.EnsureInited(buildData);
                    if (result is TypedefInfo typedef)
                    {
                        result = typedef.Typedef;
                    }
                    break;
                }

                result = FindApiTypeInfoInner(buildData, typeInfo, child);
                if (result != null)
                {
                    break;
                }
            }
            return(result);
        }
예제 #8
0
        private static ApiTypeInfo FindApiTypeInfoInner(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo parent)
        {
            foreach (var child in parent.Children)
            {
                if (child.Name == typeInfo.Type)
                {
                    child.EnsureInited(buildData);
                    return(child);
                }

                var result = FindApiTypeInfoInner(buildData, typeInfo, child);
                if (result != null)
                {
                    return(result);
                }
            }

            return(null);
        }
        private static ApiTypeInfo FindTypedef(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo parent)
        {
            ApiTypeInfo result = null;

            foreach (var child in parent.Children)
            {
                if (child is TypedefInfo typedef && typedef.Type.Equals(typeInfo))
                {
                    result = child;
                    break;
                }

                result = FindTypedef(buildData, typeInfo, child);
                if (result != null)
                {
                    break;
                }
            }
            return(result);
        }
예제 #10
0
        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));
            }
        }
예제 #11
0
        /// <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
        }
예제 #12
0
        /// <summary>
        /// Checks if use reference when passing values of this type to via scripting API methods.
        /// </summary>
        /// <param name="buildData">The build data.</param>
        /// <param name="typeInfo">The value type information.</param>
        /// <param name="caller">The calling type. It's parent module types and references are used to find the given API type.</param>
        /// <returns>True if use reference when passing the values of this type, otherwise false.</returns>
        public static bool UsePassByReference(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller)
        {
            // Skip for pointers
            if (typeInfo.IsPtr)
            {
                return(false);
            }

            // Skip for strings
            if ((typeInfo.Type == "String" || typeInfo.Type == "StringView" || typeInfo.Type == "StringAnsi" || typeInfo.Type == "StringAnsiView") && typeInfo.GenericArgs == null)
            {
                return(false);
            }

            // Skip for collections
            if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "Dictionary" || typeInfo.Type == "HashSet") && typeInfo.GenericArgs != null)
            {
                return(false);
            }

            // Skip for BytesContainer
            if (typeInfo.Type == "BytesContainer" && typeInfo.GenericArgs == null)
            {
                return(false);
            }

            // Skip for Variant
            if (typeInfo.Type == "Variant" && typeInfo.GenericArgs == null)
            {
                return(false);
            }

            // Skip for VariantType
            if (typeInfo.Type == "VariantType" && typeInfo.GenericArgs == null)
            {
                return(false);
            }

            // Find API type info
            var apiType = FindApiTypeInfo(buildData, typeInfo, caller);

            if (apiType != null)
            {
                // Skip for scripting objects
                if (apiType.IsScriptingObject)
                {
                    return(false);
                }

                // Skip for classes
                if (apiType.IsClass)
                {
                    return(false);
                }

                // Force for structures
                if (apiType.IsStruct)
                {
                    return(true);
                }
            }

            // True for references
            if (typeInfo.IsRef)
            {
                return(true);
            }

            // Force for in-build types
            if (InBuildRefTypes.Contains(typeInfo.Type))
            {
                return(true);
            }

            return(false);
        }
예제 #13
0
        private static ModuleInfo ParseModuleInner(BuildData buildData, Module module, BuildOptions moduleOptions = null)
        {
            // Setup bindings module info descriptor
            var moduleInfo = new ModuleInfo
            {
                Module    = module,
                Name      = module.BinaryModuleName,
                Namespace = string.Empty,
            };

            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.BuildCSharp || !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 = new List <string>(moduleOptions.SourceFiles.Count / 2);

            for (int i = 0; i < moduleOptions.SourceFiles.Count; i++)
            {
                if (moduleOptions.SourceFiles[i].EndsWith(".h", StringComparison.OrdinalIgnoreCase))
                {
                    headerFiles.Add(moduleOptions.SourceFiles[i]);
                }
            }
            if (headerFiles.Count == 0)
            {
                return(moduleInfo);
            }
            if (module.Name == "Core")
            {
                // Special case for Core module to ignore API tags defines
                for (int i = 0; i < headerFiles.Count; i++)
                {
                    if (headerFiles[i].EndsWith("Config.h", StringComparison.Ordinal))
                    {
                        headerFiles.RemoveAt(i);
                        break;
                    }
                }
            }

            // Sort file paths to have stable results
            headerFiles.Sort();

            // Load cache
            using (new ProfileEventScope("LoadCache"))
            {
                if (LoadCache(ref moduleInfo, moduleOptions, headerFiles))
                {
                    buildData.ModulesInfo[module] = moduleInfo;
                    return(moduleInfo);
                }
            }

            // 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++)
                {
                    using (new ProfileEventScope(Path.GetFileName(headerFiles[i])))
                    {
                        ParseModuleInnerAsync(moduleInfo, moduleOptions, headerFiles, i);
                    }
                }
            }
            else
            {
                // Multi-threaded
                ThreadPool.GetMinThreads(out var workerThreads, out var completionPortThreads);
                if (workerThreads != concurrency)
                {
                    ThreadPool.SetMaxThreads(concurrency, completionPortThreads);
                }
                Parallel.For(0, headerFiles.Count, (i, state) =>
                {
                    using (new ProfileEventScope(Path.GetFileName(headerFiles[i])))
                    {
                        ParseModuleInnerAsync(moduleInfo, moduleOptions, headerFiles, i);
                    }
                });
            }

            // Save cache
            using (new ProfileEventScope("SaveCache"))
            {
                try
                {
                    SaveCache(moduleInfo, moduleOptions, headerFiles);
                }
                catch (Exception ex)
                {
                    Log.Error($"Failed to save API cache for module {moduleInfo.Module.Name}");
                    Log.Exception(ex);
                }
            }

            return(moduleInfo);
        }
예제 #14
0
        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);
        }
예제 #15
0
        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);
            }
        }
예제 #16
0
        /// <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)
            var moduleInfo = ParseModule(buildData, module, moduleOptions);

            bindings = new BindingsResult
            {
                UseBindings             = UseBindings(moduleInfo),
                GeneratedCppFilePath    = Path.Combine(moduleOptions.IntermediateFolder, module.Name + ".Bindings.Gen.cpp"),
                GeneratedCSharpFilePath = Path.Combine(moduleOptions.IntermediateFolder, module.Name + ".Bindings.Gen.cs"),
            };

            if (bindings.UseBindings)
            {
                buildData.Modules.TryGetValue(moduleInfo.Module, out var moduleBuildInfo);

                // Ensure that generated files are included into build
                if (!moduleBuildInfo.SourceFiles.Contains(bindings.GeneratedCSharpFilePath))
                {
                    moduleBuildInfo.SourceFiles.Add(bindings.GeneratedCSharpFilePath);
                }
            }

            // Skip if module is cached (no scripting API changed)
            if (moduleInfo.IsFromCache)
            {
                return;
            }

            // Process parsed API
            using (new ProfileEventScope("Process"))
            {
                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
            if (bindings.UseBindings)
            {
                Log.Verbose($"Generating API bindings for {module.Name} ({moduleInfo.Name})");
                using (new ProfileEventScope("Cpp"))
                    GenerateCpp(buildData, moduleInfo, ref bindings);
                using (new ProfileEventScope("CSharp"))
                    GenerateCSharp(buildData, moduleInfo, ref bindings);

                // TODO: add support for extending this code and support generating bindings for other scripting languages
            }
        }