// ----------------------------------------------------------------------------- // file="ManagedApiPlanner" // Project: StgSharp // Copyright (c) Nitload. // SPDX-License-Identifier: MIT // ----------------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using StgSharp.GenerateGL.Analysis; using StgSharp.GenerateGL.Naming; using StgSharp.GenerateGL.Registry; namespace StgSharp.GenerateGL.Generation { internal sealed class ManagedApiPlanner { private readonly IReadOnlyList _filters = Array.AsReadOnly( new IGlApiFilter[] { new GlManagedApiSuppressionFilter(), new GlOverloadApiFilter(), new GlGetValueApiFilter(), new GlHandleApiFilter(), }); private readonly GlEquivalentTypeMapper _typeMapper; private readonly GlEnumTypeMap _enumTypeMap; private readonly GlManagedApiNamePolicy _namePolicy; public ManagedApiPlanner( GlEquivalentTypeMapper typeMapper, GlFunctionFamilyAnalysis familyAnalysis, GlEnumTypeMap enumTypeMap ) { ArgumentNullException.ThrowIfNull(typeMapper); ArgumentNullException.ThrowIfNull(familyAnalysis); ArgumentNullException.ThrowIfNull(enumTypeMap); _typeMapper = typeMapper; _enumTypeMap = enumTypeMap; _namePolicy = new GlManagedApiNamePolicy(familyAnalysis); } public IReadOnlyList Plan( IReadOnlyList commands ) { ArgumentNullException.ThrowIfNull(commands); List result = new List(); foreach (GlCommandDefinition command in commands) { IReadOnlyList methods = Array.AsReadOnly( new[] { CreateRawMethod(command), }); foreach (IGlApiFilter filter in _filters) { methods = filter.Apply(command, methods, _typeMapper); } methods = ApplyEnumTypes(command, methods); result.AddRange( methods.Select( method => method with { Name = _namePolicy.ReadMethodName(command, method.Name), DocumentationPage = _namePolicy.ReadDocumentationPage(command), Parameters = Array.AsReadOnly( method.Parameters.Select( parameter => parameter with { Name = CSharpIdentifierPolicy.NormalizeParameterName( parameter.Name), }).ToArray()), })); } ResolveNameCollisions(result); return Array.AsReadOnly(result.ToArray()); } private ReadOnlyCollection ApplyEnumTypes( GlCommandDefinition command, IReadOnlyList methods ) { bool hasEnumReturn = _enumTypeMap.TryReadReturnType( command.Return, out string enumReturnType); return Array.AsReadOnly( methods.Select( method => ConvertEnumReturn(method, hasEnumReturn, enumReturnType)).ToArray()); } private ManagedMethodDefinition ConvertEnumReturn( ManagedMethodDefinition method, bool hasEnumReturn, string enumReturnType ) { string returnType = string.Empty; bool isEnumReturn = hasEnumReturn && TryReplaceNativeEnumType( method.ReturnType, enumReturnType, out returnType); return method with { ReturnType = isEnumReturn ? returnType : method.ReturnType, IsEnumReturn = isEnumReturn, Parameters = Array.AsReadOnly( method.Parameters.Select(ConvertEnumParameter).ToArray()), }; } private ManagedParameterDefinition ConvertEnumParameter( ManagedParameterDefinition parameter ) { if (parameter.NativeParameter is null || !_enumTypeMap.TryReadParameterType( parameter.NativeParameter, out string enumType) || !TryReplaceNativeEnumType(parameter.Type, enumType, out string convertedType)) { return parameter; } return parameter with { Type = convertedType, IsEnum = true, }; } private static bool TryReplaceNativeEnumType( string nativeType, string enumType, out string convertedType ) { const string nativeBaseType = "uint"; if (!nativeType.StartsWith(nativeBaseType, StringComparison.Ordinal) || nativeType.AsSpan(nativeBaseType.Length).ContainsAnyExcept('*')) { convertedType = string.Empty; return false; } convertedType = enumType + nativeType[nativeBaseType.Length..]; return true; } public static void Validate( IEnumerable methods ) { ArgumentNullException.ThrowIfNull(methods); Dictionary signatures = new Dictionary( StringComparer.Ordinal); foreach (ManagedMethodDefinition method in methods) { string signature = $"{method.Name}({string.Join(",", method.Parameters.Select(value => value.Type))})"; if (signatures.TryGetValue(signature, out ManagedMethodDefinition? previous)) { throw new InvalidOperationException( $"Managed overload '{signature}' is produced by both " + $"'{previous.NativeCommand.Name}' and '{method.NativeCommand.Name}'."); } signatures.Add(signature, method); } } private static void ResolveNameCollisions( List methods ) { ILookup indicesBySignature = methods .Select( (method, index) => new { Index = index, Signature = ReadManagedSignature(method), }) .ToLookup(value => value.Signature, value => value.Index, StringComparer.Ordinal); foreach (IGrouping group in indicesBySignature) { int[] indices = group.ToArray(); if (indices.Length < 2) { continue; } string[] nativeNames = indices .Select(value => methods[value].NativeCommand.Name) .Distinct(StringComparer.Ordinal) .ToArray(); if (nativeNames.Length != indices.Length) { throw new InvalidOperationException( $"Managed overload '{group.Key}' is duplicated by one native command."); } foreach (int index in indices) { ManagedMethodDefinition method = methods[index]; methods[index] = method with { Name = GlManagedApiNamePolicy.ReadNativeMethodName( method.NativeCommand), }; } } } private static string ReadManagedSignature( ManagedMethodDefinition method ) { return $"{method.Name}({string.Join(",", method.Parameters.Select(value => value.Type))})"; } private ManagedMethodDefinition CreateRawMethod( GlCommandDefinition command ) { List parameters = new List( command.Parameters.Count); List arguments = new List( command.Parameters.Count); for (int index = 0; index < command.Parameters.Count; index++) { GlParameterDefinition source = command.Parameters[index]; parameters.Add( new ManagedParameterDefinition( _typeMapper.ReadParameterType(source), source.Name, source)); arguments.Add(new ParameterArgument(index)); } return new ManagedMethodDefinition( ReadMethodName(command.Name), _typeMapper.ReadReturnType(command), Array.AsReadOnly(parameters.ToArray()), command, new DirectInvocation(Array.AsReadOnly(arguments.ToArray()))); } private static string ReadMethodName( string commandName ) { return commandName.StartsWith("gl", StringComparison.Ordinal) && commandName.Length > 2 ? commandName[2..] : commandName; } } }