Files
stgsharp/tools/Nitload.GenerateGL/Generation/ManagedApiPlanner.cs
T

269 lines
9.8 KiB
C#

// -----------------------------------------------------------------------------
// 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<IGlApiFilter> _filters = Array.AsReadOnly<IGlApiFilter>(
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<ManagedMethodDefinition> Plan(
IReadOnlyList<GlCommandDefinition> commands
)
{
ArgumentNullException.ThrowIfNull(commands);
List<ManagedMethodDefinition> result = new List<ManagedMethodDefinition>();
foreach (GlCommandDefinition command in commands)
{
IReadOnlyList<ManagedMethodDefinition> 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<ManagedMethodDefinition> ApplyEnumTypes(
GlCommandDefinition command,
IReadOnlyList<ManagedMethodDefinition> 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<ManagedMethodDefinition> methods
)
{
ArgumentNullException.ThrowIfNull(methods);
Dictionary<string, ManagedMethodDefinition> signatures = new Dictionary<string, ManagedMethodDefinition>(
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<ManagedMethodDefinition> methods
)
{
ILookup<string, int> indicesBySignature = methods
.Select(
(method, index) => new
{
Index = index,
Signature = ReadManagedSignature(method),
})
.ToLookup(value => value.Signature, value => value.Index, StringComparer.Ordinal);
foreach (IGrouping<string, int> 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<ManagedParameterDefinition> parameters = new List<ManagedParameterDefinition>(
command.Parameters.Count);
List<ManagedArgument> arguments = new List<ManagedArgument>(
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;
}
}
}