Esempio n. 1
0
        /// <summary>
        /// Writes the given profile to a directory.
        /// </summary>
        /// <param name="generatorSettings">The generator settings to use for the profile writer.</param>
        /// <param name="profile">The profile to write.</param>
        /// <param name="docs">The profile's documentation.</param>
        /// <param name="nc">The name container for this profile.</param>
        public static void Write
        (
            IGeneratorSettings generatorSettings,
            ApiProfile profile,
            ProfileDocumentation docs,
            NameContainer nc
        )
        {
            var rootFolder    = Path.Combine(Program.Arguments.OutputPath, generatorSettings.OutputSubfolder);
            var rootNamespace = generatorSettings.Namespace;

            if (!Directory.Exists(rootFolder))
            {
                Directory.CreateDirectory(rootFolder);
            }

            if (!Directory.Exists(Path.Combine(rootFolder, rootNamespace)))
            {
                Directory.CreateDirectory(Path.Combine(rootFolder, rootNamespace));
            }

            if (!Directory.Exists(Path.Combine(rootFolder, ExtensionsFolder)))
            {
                Directory.CreateDirectory(Path.Combine(rootFolder, ExtensionsFolder));
            }

            foreach (var project in GetProjects(profile))
            {
                WriteProject(project, generatorSettings, nc, docs);
            }
        }
Esempio n. 2
0
        /// <summary>
        /// Initializes a new instance of the <see cref="BindingWriter"/> class.
        /// </summary>
        /// <param name="generatorSettings">The generator settings.</param>
        /// <param name="profile">The profile.</param>
        /// <param name="documentation">The documentation for the profile.</param>
        public BindingWriter
        (
            [NotNull] IGeneratorSettings generatorSettings,
            [NotNull] ApiProfile profile,
            [NotNull] ProfileDocumentation documentation
        )
        {
            _generatorSettings = generatorSettings ?? throw new ArgumentNullException(nameof(generatorSettings));
            _profile           = profile ?? throw new ArgumentNullException(nameof(profile));
            _documentation     = documentation ?? throw new ArgumentNullException(nameof(documentation));

            var allFunctions      = profile.NativeSignatures.Concat(profile.Overloads).ToList();
            var enumerationUsages = new Dictionary <EnumerationSignature, IReadOnlyList <FunctionSignature> >();

            foreach (var e in profile.Enumerations)
            {
                // Initialize the dictionary
                var functionsUsingThisEnum = allFunctions
                                             .Where(f => f.Parameters.Any(p => p.Type.Name == e.Name))
                                             .ToList();

                enumerationUsages.Add(e, functionsUsingThisEnum);
            }

            _enumerationUsages = enumerationUsages;

            _entrypointSlots = profile.NativeSignatures
                               .Select((ns, i) => (ns.NativeEntrypoint, i))
                               .ToDictionary(t1 => t1.Item1, t2 => t2.Item2);

            _identifierTranslator = new NativeIdentifierTranslator();
        }
        public ProfileDocumentation BakeDocumentation([NotNull] ProfileDocumentation documentation)
        {
            var bakedFunctions = new List <FunctionDocumentation>();

            foreach (var function in documentation.Functions)
            {
                var functionNameWithoutPrefix = new string(function.Name.SkipWhile(char.IsLower).ToArray());

                var actualFunction =
                    _apiProfile.FindFunctionWithEntrypoint(functionNameWithoutPrefix);
                if (actualFunction is null)
                {
                    // This function isn't a part of the profile
                    continue;
                }

                bakedFunctions.Add(BakeFunctionDocumentation(function));
            }

            return(new ProfileDocumentation(bakedFunctions));
        }
Esempio n. 4
0
        /// <summary>
        /// Asynchronously writes the given profile to a directory.
        /// </summary>
        /// <param name="generatorSettings">The generator settings to use for the profile writer.</param>
        /// <param name="profile">The profile to write.</param>
        /// <param name="docs">The profile's documentation.</param>
        /// <param name="nc">The name container for this profile.</param>
        /// <returns>An asynchronous task.</returns>
        public static Task WriteAsync
        (
            IGeneratorSettings generatorSettings,
            ApiProfile profile,
            ProfileDocumentation docs,
            NameContainer nc
        )
        {
            var rootFolder    = Path.Combine(Program.Arguments.OutputPath, generatorSettings.OutputSubfolder);
            var rootNamespace = generatorSettings.Namespace;

            if (!Directory.Exists(rootFolder))
            {
                Directory.CreateDirectory(rootFolder);
            }

            if (!Directory.Exists(Path.Combine(rootFolder, rootNamespace)))
            {
                Directory.CreateDirectory(Path.Combine(rootFolder, rootNamespace));
            }

            if (!Directory.Exists(Path.Combine(rootFolder, ExtensionsFolder)))
            {
                Directory.CreateDirectory(Path.Combine(rootFolder, ExtensionsFolder));
            }

            return(Task.WhenAll
                   (
                       GetProjects(profile)
                       .Select
                       (
                           x => WriteProjectAsync(
                               x,
                               generatorSettings,
                               nc,
                               docs)
                       )
                   ));
        }
Esempio n. 5
0
        /// <summary>
        /// Asynchronously writes this interface to a file.
        /// </summary>
        /// <param name="i">The interface.</param>
        /// <param name="file">The file to write to.</param>
        /// <param name="ns">This interface's namespace.</param>
        /// <param name="prefix">The function prefix for this interface.</param>
        /// <param name="doc">The profile's documentation.</param>
        /// <param name="rootNamespace">The profile's root namespace.</param>
        /// <returns>The asynchronous task.</returns>
        public static async Task WriteInterfaceAsync(this Interface i, string file, string ns, string prefix, ProfileDocumentation doc, string rootNamespace)
        {
            using (var sw = new StreamWriter(File.Open(file, FileMode.Create, FileAccess.ReadWrite, FileShare.Inheritable)))
            {
                sw.WriteLine("// <auto-generated />");
                sw.WriteLine(EmbeddedResources.LicenseText(Path.GetFileName(file)));
                sw.WriteLine("using AdvancedDLSupport;");
                sw.WriteLine("using " + rootNamespace + ";");
                sw.WriteLine("using OpenToolkit.Core.Native;");
                sw.WriteLine("using System;");
                sw.WriteLine("using System.Runtime.InteropServices;");
                sw.WriteLine("using System.Text;");
                sw.WriteLine();
                sw.WriteLine("namespace " + ns);
                sw.WriteLine("{");
                sw.WriteLine("    internal interface " + i.InterfaceName);
                sw.Write("    {");
                foreach (var function in i.Functions)
                {
                    sw.WriteLine();
                    using (var sr = new StringReader(Utilities.GetDocumentation(function, doc)))
                    {
                        string line;
                        while ((line = await sr.ReadLineAsync()) != null)
                        {
                            sw.WriteLine("        " + line);
                        }
                    }

                    if (function.IsDeprecated)
                    {
                        var str = "Deprecated";
                        if (function.DeprecatedIn != null)
                        {
                            str += " since " + function.DeprecatedIn.ToString(2);
                        }

                        if (!string.IsNullOrWhiteSpace(function.DeprecationReason))
                        {
                            str += " - " + function.DeprecationReason;
                        }

                        sw.WriteLine($"        [Obsolete(\"{str}\")]");
                    }

                    sw.WriteLine
                    (
                        "        [NativeSymbol(\"" + prefix + function.NativeEntrypoint + "\")]"
                    );

                    sw.WriteLine
                    (
                        "        " +
                        "[AutoGenerated(" +
                        $"Category = \"{function.Categories.First()}\", " +
                        $"Version = \"{function.IntroducedIn}\", " +
                        $"EntryPoint = \"{prefix}{function.NativeEntrypoint}\", " +
                        $"Source = \"{function.Source}\"" +
                        ")]"
                    );

                    using (var sr = new StringReader(Utilities.GetDeclarationString(function) + ";"))
                    {
                        string line;
                        while ((line = await sr.ReadLineAsync()) != null)
                        {
                            sw.WriteLine("        " + line);
                        }
                    }
                }

                sw.WriteLine("    }");
                sw.WriteLine("}");
                await sw.FlushAsync();
            }
        }
Esempio n. 6
0
        private static void WriteProject
        (
            Project project,
            IGeneratorSettings settings,
            NameContainer nc,
            ProfileDocumentation doc
        )
        {
            var folder = Path.Combine(Program.Arguments.OutputPath, settings.OutputSubfolder);
            var ns     = project.Extension == "Core" ? settings.Namespace : settings.ExtensionNamespace + "." + Utilities.ConvertExtensionNameToNamespace(project.Extension);
            var dir    = project.Extension == "Core"
                ? Path.Combine(folder, ns, settings.ClassName)
                : Path.Combine(folder, ExtensionsFolder, ns);

            if (!Directory.Exists(dir))
            {
                Directory.CreateDirectory(dir);
            }

            if (!Directory.Exists(Path.Combine(dir, InterfacesFolder)))
            {
                Directory.CreateDirectory(Path.Combine(dir, InterfacesFolder));
            }

            if (!Directory.Exists(Path.Combine(dir, EnumsFolder)))
            {
                Directory.CreateDirectory(Path.Combine(dir, EnumsFolder));
            }

            foreach (var interfaceDef in project.Interfaces)
            {
                var path = Path.Combine(dir, InterfacesFolder, interfaceDef.InterfaceName + ".cs");
                InterfaceWriter.WriteInterface(interfaceDef, path, ns, settings.FunctionPrefix, doc, settings.Namespace);
            }

            foreach (var enumSignature in project.Enums)
            {
                var path = Path.Combine(dir, EnumsFolder, enumSignature.Name + ".cs");
                EnumWriter.WriteEnum(enumSignature, path, ns, settings.ConstantPrefix);
            }

            if (project.Extension == "Core")
            {
                InterfaceWriter.WriteMetaInterface
                (
                    ns,
                    Path.Combine(dir, InterfacesFolder, "I" + settings.ClassName + ".cs"),
                    settings.ClassName,
                    project.Interfaces.Select(x => x.InterfaceName)
                );

                NameContainerWriter.WriteNameContainer
                (
                    Path.Combine(dir, $"{settings.APIIdentifier}LibraryNameContainer.cs"),
                    ns,
                    settings.APIIdentifier,
                    nc
                );
            }
            else
            {
                // we expect the project file to already be created
                ProjectFileWriter.WriteProjectFile(ns, dir, settings.OutputSubfolder, settings.Namespace, project.Extension != "Core");
            }

            ClassWriter.WriteMixedModeClass(project, settings, doc);
        }
Esempio n. 7
0
        private static async Task WriteProjectAsync
        (
            Project project,
            IGeneratorSettings settings,
            NameContainer nc,
            ProfileDocumentation doc
        )
        {
            var folder = Path.Combine(Program.Arguments.OutputPath, settings.OutputSubfolder);
            var ns     = project.Extension == "Core" ? settings.Namespace : settings.ExtensionNamespace + "." + Utilities.ConvertExtensionNameToNamespace(project.Extension);
            var dir    = project.Extension == "Core"
                ? Path.Combine(folder, ns, settings.ClassName)
                : Path.Combine(folder, ExtensionsFolder, ns);

            if (!Directory.Exists(dir))
            {
                Directory.CreateDirectory(dir);
            }

            if (!Directory.Exists(Path.Combine(dir, InterfacesFolder)))
            {
                Directory.CreateDirectory(Path.Combine(dir, InterfacesFolder));
            }

            if (!Directory.Exists(Path.Combine(dir, EnumsFolder)))
            {
                Directory.CreateDirectory(Path.Combine(dir, EnumsFolder));
            }

            await Task.WhenAll(
                project.Interfaces.Select
                (
                    x => InterfaceWriter.WriteInterfaceAsync
                    (
                        x, Path.Combine(dir, InterfacesFolder, x.InterfaceName + ".cs"), ns, settings.FunctionPrefix, doc, settings.Namespace
                    )
                ).Concat
                (
                    project.Enums.Select(
                        x => EnumWriter.WriteEnumAsync
                        (
                            x,
                            Path.Combine(dir, EnumsFolder, x.Name + ".cs"),
                            ns,
                            settings.ConstantPrefix
                        )
                        )
                )
                );

            if (project.Extension == "Core")
            {
                await InterfaceWriter.WriteMetaInterfaceAsync
                (
                    ns,
                    Path.Combine(dir, InterfacesFolder, "I" + settings.ClassName + ".cs"),
                    settings.ClassName,
                    project.Interfaces.Select(x => x.InterfaceName)
                );

                await NameContainerWriter.WriteNameContainerAsync
                (
                    Path.Combine(dir, $"{settings.APIIdentifier}LibraryNameContainer.cs"),
                    ns,
                    settings.APIIdentifier,
                    nc
                );
            }
            else
            {
                // we expect the project file to already be created
                await ProjectFileWriter.WriteProjectFileAsync(ns, dir, settings.OutputSubfolder, settings.Namespace, project.Extension != "Core");
            }

            await ClassWriter.WriteMixedModeClassAsync(project, settings, doc);
        }
Esempio n. 8
0
        /// <summary>
        /// Gets the XML documentation for the given function, using the given <see cref="ProfileDocumentation"/> as a
        /// source for documentation.
        /// </summary>
        /// <param name="function">The function to which the returned documentation regards.</param>
        /// <param name="doc">The documentation source.</param>
        /// <returns>The XML documentation for the given function.</returns>
        public static string GetDocumentation(FunctionSignature function, ProfileDocumentation doc)
        {
            var sw = new StringWriter();

            if (!doc.TryGetDocumentation(function, out var documentation))
            {
                Debug.WriteLine(
                    $"The function \"{function.Name}\" lacks documentation. Consider adding a documentation file for " +
                    "the function.");

                WritePlaceholderDocumentation(sw, function);
                return(sw.ToString());
            }

            sw.WriteLine("/// <summary>");
            var summaryLines = documentation.Purpose.TrimEnd().Split('\n');

            foreach (var summaryLine in summaryLines)
            {
                sw.WriteLine($"/// {summaryLine}");
            }

            sw.WriteLine("/// </summary>");

            foreach (var parameter in function.Parameters)
            {
                var parameter1             = parameter;
                var parameterDocumentation = documentation.Parameters.FirstOrDefault(dp => dp.Name == parameter1.Name);
                if (parameterDocumentation is null)
                {
                    sw.WriteLine($"/// <param name=\"{parameter.Name}\">See summary.</param>");
                    continue;
                }

                // XML documentation doesn't require keyword escaping.
                if (parameter.Name.TrimStart('@') != parameterDocumentation.Name)
                {
                    Debug.WriteLine(
                        $"Parameter {parameter.Name} in function {function.Name} has incorrect documentation name " +
                        $"{parameterDocumentation.Name}.");
                }

                sw.WriteLine($"/// <param name=\"{parameterDocumentation.Name}\">");
                if (!(parameter.Count is null))
                {
                    if (parameter.Count.IsStatic)
                    {
                        sw.WriteLine($"/// This parameter contains {parameter.Count.Count} elements.");
                        sw.WriteLine("///");
                    }

                    if (parameter.Count.IsComputed)
                    {
                        var parameterList = parameter.Count.ComputedFrom.Select(cf => cf.Name).Humanize();
                        sw.WriteLine($"/// This parameter's element count is computed from {parameterList}.");
                        sw.WriteLine("///");
                    }

                    if (parameter.Count.IsReference)
                    {
                        // ReSharper disable once PossibleNullReferenceException
                        sw.WriteLine($"/// This parameter's element count is taken from {parameter.Count.ValueReference.Name}.");
                        sw.WriteLine("///");
                    }
                }

                var descriptionLines = parameterDocumentation.Description.TrimEnd().Split('\n');
                foreach (var descriptionLine in descriptionLines)
                {
                    sw.Write($"/// {descriptionLine}");
                }

                sw.WriteLine();
                sw.WriteLine("/// </param>");
            }

            foreach (var genericTypeParameter in function.GenericTypeParameters)
            {
                var referencingParameter = function.Parameters.First(f => f.Type.Name == genericTypeParameter.Name);
                sw.WriteLine(
                    $"/// <typeparam name=\"{genericTypeParameter.Name}\">The generic type of " +
                    $"{referencingParameter.Name}.</typeparam>");
            }

            if (!function.ReturnType.Name.Equals(typeof(void).Name, StringComparison.OrdinalIgnoreCase))
            {
                sw.WriteLine("/// <returns>See summary.</returns>");
            }

            return(sw.ToString());
        }
Esempio n. 9
0
        /// <summary>
        /// Writes a mixed mode class for the given project.
        /// </summary>
        /// <param name="project">The project to write a mixed-mode class for.</param>
        /// <param name="settings">The generator settings used to configure the mixed-mode class writer.</param>
        /// <param name="docs">The profile's documentation, used to write summaries to overloads.</param>
        /// <returns>An asynchronous task.</returns>
        public static async Task WriteMixedModeClassAsync(Project project, IGeneratorSettings settings, ProfileDocumentation docs)
        {
            var ext = project.Extension != "Core";
            var ns  = project.Extension == "Core"
                ? settings.Namespace
                : settings.ExtensionNamespace + "." + Utilities.ConvertExtensionNameToNamespace(project.Extension);
            var dir = project.Extension == "Core"
                ? Path.Combine(Program.Arguments.OutputPath, settings.OutputSubfolder, settings.Namespace, settings.ClassName)
                : Path.Combine(Program.Arguments.OutputPath, settings.OutputSubfolder, ProfileWriter.ExtensionsFolder, ns);

            await WriteOverloadsMixedModePartAsync(project, settings, docs);
            await WriteNativeMixedModePartAsync(project, settings);

            if (!File.Exists(Path.Combine(dir, $"{settings.ClassName}.cs")) && !ext)
            {
                await WriteTemplateMixedModePartAsync(project, settings);
            }
        }
Esempio n. 10
0
        private static async Task WriteOverloadsMixedModePartAsync(Project project, IGeneratorSettings settings, ProfileDocumentation docs)
        {
            var file = project.Extension == "Core"
                ? settings.ClassName
                : NativeIdentifierTranslator.TranslateIdentifierName(project.Extension);
            var ns = project.Extension == "Core"
                ? settings.Namespace
                : settings.ExtensionNamespace + "." + Utilities.ConvertExtensionNameToNamespace(project.Extension);
            var dir = project.Extension == "Core"
                ? Path.Combine(Program.Arguments.OutputPath, settings.OutputSubfolder, settings.Namespace, settings.ClassName)
                : Path.Combine(Program.Arguments.OutputPath, settings.OutputSubfolder, ProfileWriter.ExtensionsFolder, ns);

            using (var sw = new StreamWriter(File.Open(Path.Combine(dir, file + ".Overloads.cs"), FileMode.Create, FileAccess.ReadWrite, FileShare.Inheritable)))
            {
                sw.WriteLine("// <auto-generated />");
                sw.WriteLine(EmbeddedResources.LicenseText(Path.GetFileName(file)));
                sw.WriteLine("using AdvancedDLSupport;");
                sw.WriteLine("using OpenToolkit.Core.Native;");
                sw.WriteLine("using OpenToolkit.Core.Extensions;");
                sw.WriteLine("using System;");
                sw.WriteLine("using System.Runtime.InteropServices;");
                sw.WriteLine("using System.Text;");
                sw.WriteLine("using " + settings.Namespace + ";");
                sw.WriteLine();
                sw.WriteLine("namespace " + ns);
                sw.WriteLine("{");
                sw.WriteLine("    /// <summary>");
                sw.WriteLine("    /// Contains bindings to the " + settings.APIIdentifier + " API.");
                sw.WriteLine("    /// </summary>");
                sw.WriteLine("    public partial class " + file);
                sw.WriteLine("    {");
                foreach (var overload in project.Overloads)
                {
                    sw.WriteLine("        /// <summary>");
                    if (docs.HasDocumentation(overload.Item1))
                    {
                        sw.WriteLine("        /// " + docs.GetDocumentation(overload.Item1)?.Purpose);
                    }
                    else
                    {
                        sw.WriteLine("        /// To be added.");
                    }

                    sw.WriteLine("        /// </summary>");

                    if (overload.Item1.IsDeprecated)
                    {
                        var str = "Deprecated";
                        if (overload.Item1.DeprecatedIn != null)
                        {
                            str += " since " + overload.Item1.DeprecatedIn.ToString(2);
                        }

                        if (!string.IsNullOrWhiteSpace(overload.Item1.DeprecationReason))
                        {
                            str += " - " + overload.Item1.DeprecationReason;
                        }

                        sw.WriteLine($"        [Obsolete(\"{str}\")]");
                    }

                    sw.WriteLine(
                        "        " +
                        "[AutoGenerated(" +
                        $"Category = \"{overload.Item1.Categories.First()}\", " +
                        $"Version = \"{overload.Item1.IntroducedIn}\", " +
                        $"EntryPoint = \"{settings.FunctionPrefix}{overload.Item1.NativeEntrypoint}\", " +
                        $"Source = \"{overload.Item1.Source}\"" +
                        ")]");

                    sw.Write("        public ");
                    var decl = Utilities.GetDeclarationString(overload.Item1);
                    if (!decl.Contains("unsafe "))
                    {
                        sw.Write("unsafe ");
                    }

                    sw.WriteLine(decl);
                    sw.WriteLine("        {");
                    foreach (var line in overload.Item2.ToString()
                             .Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries))
                    {
                        sw.Write("            " + line);
                    }

                    sw.WriteLine("        }");
                    sw.WriteLine();
                }

                sw.WriteLine("    }");
                sw.WriteLine("}");
                await sw.FlushAsync();
            }
        }