/// <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); } }
/// <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)); }
/// <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) ) )); }
/// <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(); } }
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); }
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); }
/// <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()); }
/// <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); } }
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(); } }