430 lines
17 KiB
C#
430 lines
17 KiB
C#
// -----------------------------------------------------------------------------
|
|
// file="GlFunctionFamilyAnalyzer"
|
|
// 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.Naming;
|
|
using StgSharp.GenerateGL.Registry;
|
|
|
|
namespace StgSharp.GenerateGL.Analysis
|
|
{
|
|
/// <summary>
|
|
/// Builds inspectable function-family candidates without changing generation.
|
|
/// </summary>
|
|
internal static class GlFunctionFamilyAnalyzer
|
|
{
|
|
|
|
public static GlFunctionFamilyAnalysis Analyze(
|
|
GlRegistryModel registry
|
|
)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(registry);
|
|
return Analyze(registry, registry.Commands);
|
|
}
|
|
|
|
public static GlFunctionFamilyAnalysis Analyze(
|
|
GlRegistryProjection projection
|
|
)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(projection);
|
|
return Analyze(projection.Source, projection.Commands);
|
|
}
|
|
|
|
private static GlFunctionFamilyAnalysis Analyze(
|
|
GlRegistryModel registry,
|
|
IReadOnlyList<GlCommandDefinition> commands
|
|
)
|
|
{
|
|
Dictionary<string, IReadOnlyList<GlCommandDefinition>> commandsByName = commands
|
|
.GroupBy(value => value.Name, StringComparer.Ordinal)
|
|
.ToDictionary(
|
|
value => value.Key,
|
|
value => (IReadOnlyList<GlCommandDefinition>)Array.AsReadOnly(
|
|
value.ToArray()),
|
|
StringComparer.Ordinal);
|
|
|
|
Dictionary<string, GlFunctionNameShape> shapes =
|
|
GlFunctionNameFactorizer.Factor(registry, commands);
|
|
Dictionary<string, FamilyBuilder> builders = new Dictionary<string, FamilyBuilder>(
|
|
StringComparer.Ordinal);
|
|
List<GlFunctionRelation> relations = new List<GlFunctionRelation>();
|
|
|
|
foreach (GlCommandDefinition command in commands)
|
|
{
|
|
if (shapes.TryGetValue(command.Name, out GlFunctionNameShape? shape))
|
|
{
|
|
ReadBuilder(builders, shape.FamilyName).Add(command, shape);
|
|
}
|
|
}
|
|
|
|
AddRegistryRelations(commands, commandsByName, shapes, builders, relations);
|
|
AddSingleValuePointerRelations(commandsByName, shapes, builders, relations);
|
|
|
|
GlFunctionFamily[] families = builders.Values
|
|
.Where(value => value.Members.Count > 1 || value.Relations.Count != 0)
|
|
.Select(value => value.Build())
|
|
.OrderBy(value => value.Name, StringComparer.Ordinal)
|
|
.ToArray();
|
|
HashSet<string> classifiedNames = families
|
|
.SelectMany(value => value.Members)
|
|
.Select(value => value.Command.Name)
|
|
.ToHashSet(StringComparer.Ordinal);
|
|
GlCommandDefinition[] unclassified = commands
|
|
.Where(value => !classifiedNames.Contains(value.Name))
|
|
.OrderBy(value => value.Name, StringComparer.Ordinal)
|
|
.ToArray();
|
|
|
|
return new GlFunctionFamilyAnalysis(
|
|
Freeze(families),
|
|
Freeze(unclassified),
|
|
Freeze(relations));
|
|
}
|
|
|
|
private static void AddRegistryRelations(
|
|
IReadOnlyList<GlCommandDefinition> commands,
|
|
Dictionary<string, IReadOnlyList<GlCommandDefinition>> commandsByName,
|
|
Dictionary<string, GlFunctionNameShape> shapes,
|
|
IDictionary<string, FamilyBuilder> builders,
|
|
List<GlFunctionRelation> relations
|
|
)
|
|
{
|
|
foreach (GlCommandDefinition command in commands)
|
|
{
|
|
foreach (GlNamedReference reference in command.VectorEquivalents)
|
|
{
|
|
bool exists = commandsByName.TryGetValue(
|
|
reference.Name,
|
|
out IReadOnlyList<GlCommandDefinition>? targets);
|
|
GlFunctionRelation relation = new GlFunctionRelation(
|
|
command.Name,
|
|
reference.Name,
|
|
GlFunctionRelationKind.RegistryVectorEquivalent,
|
|
exists
|
|
? GlFunctionRelationState.Verified
|
|
: GlFunctionRelationState.MissingTarget,
|
|
exists
|
|
? "declared by gl.xml <vecequiv>"
|
|
: "gl.xml <vecequiv> target is not registered");
|
|
relations.Add(relation);
|
|
|
|
string familyName = ReadRelationFamilyName(
|
|
command.Name,
|
|
reference.Name,
|
|
shapes);
|
|
FamilyBuilder builder = ReadBuilder(builders, familyName);
|
|
builder.Add(
|
|
command,
|
|
shapes.TryGetValue(command.Name, out GlFunctionNameShape? sourceShape)
|
|
? sourceShape
|
|
: null);
|
|
builder.Relations.Add(relation);
|
|
if (!exists)
|
|
{
|
|
builder.Diagnostics.Add(
|
|
$"Missing vector-equivalent target '{reference.Name}'.");
|
|
continue;
|
|
}
|
|
|
|
foreach (GlCommandDefinition target in targets!)
|
|
{
|
|
builder.Add(
|
|
target,
|
|
shapes.TryGetValue(target.Name, out GlFunctionNameShape? targetShape)
|
|
? targetShape
|
|
: null);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void AddSingleValuePointerRelations(
|
|
Dictionary<string, IReadOnlyList<GlCommandDefinition>> commandsByName,
|
|
Dictionary<string, GlFunctionNameShape> shapes,
|
|
IDictionary<string, FamilyBuilder> builders,
|
|
List<GlFunctionRelation> relations
|
|
)
|
|
{
|
|
HashSet<string> existingPairs = relations
|
|
.Select(value => $"{value.SourceName}\0{value.TargetName}")
|
|
.ToHashSet(StringComparer.Ordinal);
|
|
foreach ((string commandName, GlFunctionNameShape pointerShape) in shapes)
|
|
{
|
|
if (!pointerShape.IsVectorForm ||
|
|
!commandsByName.TryGetValue(
|
|
commandName,
|
|
out IReadOnlyList<GlCommandDefinition>? pointerCommands))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string scalarName = commandName[..^1];
|
|
if (!shapes.TryGetValue(scalarName, out GlFunctionNameShape? scalarShape) ||
|
|
!commandsByName.TryGetValue(
|
|
scalarName,
|
|
out IReadOnlyList<GlCommandDefinition>? scalarCommands))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (GlCommandDefinition pointer in pointerCommands)
|
|
{
|
|
if (pointer.Parameters.Count == 0 ||
|
|
!string.Equals(pointer.Parameters[^1].Length, "1", StringComparison.Ordinal))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (GlCommandDefinition scalar in scalarCommands)
|
|
{
|
|
if (existingPairs.Contains($"{scalar.Name}\0{pointer.Name}"))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool valid = IsSingleValuePointerPair(scalar, pointer, out string detail);
|
|
GlFunctionRelation relation = new GlFunctionRelation(
|
|
scalar.Name,
|
|
pointer.Name,
|
|
GlFunctionRelationKind.InferredSingleValuePointer,
|
|
valid
|
|
? GlFunctionRelationState.Verified
|
|
: GlFunctionRelationState.Rejected,
|
|
detail);
|
|
relations.Add(relation);
|
|
|
|
FamilyBuilder builder = ReadBuilder(builders, pointerShape.FamilyName);
|
|
builder.Add(scalar, scalarShape);
|
|
builder.Add(pointer, pointerShape);
|
|
builder.Relations.Add(relation);
|
|
if (!valid)
|
|
{
|
|
builder.Diagnostics.Add(
|
|
$"Rejected scalar/pointer pair {scalar.Name} -> {pointer.Name}: " +
|
|
detail);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static bool IsSingleValuePointerPair(
|
|
GlCommandDefinition scalar,
|
|
GlCommandDefinition pointer,
|
|
out string detail
|
|
)
|
|
{
|
|
if (!string.Equals(
|
|
scalar.Return.TypeName,
|
|
pointer.Return.TypeName,
|
|
StringComparison.Ordinal) ||
|
|
scalar.Parameters.Count != pointer.Parameters.Count)
|
|
{
|
|
detail = "return type or parameter count differs";
|
|
return false;
|
|
}
|
|
|
|
int lastIndex = scalar.Parameters.Count - 1;
|
|
for (int index = 0; index < lastIndex; index++)
|
|
{
|
|
if (!HaveEquivalentParameterTypes(
|
|
scalar.Parameters[index],
|
|
pointer.Parameters[index]))
|
|
{
|
|
detail = $"parameter {index + 1} differs";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
GlParameterDefinition scalarValue = scalar.Parameters[lastIndex];
|
|
GlParameterDefinition pointerValue = pointer.Parameters[lastIndex];
|
|
if (!string.Equals(
|
|
scalarValue.TypeName,
|
|
pointerValue.TypeName,
|
|
StringComparison.Ordinal) ||
|
|
scalarValue.DeclarationText.Contains('*', StringComparison.Ordinal) ||
|
|
!pointerValue.DeclarationText.Contains('*', StringComparison.Ordinal) ||
|
|
!pointerValue.DeclarationText.TrimStart().StartsWith(
|
|
"const ",
|
|
StringComparison.Ordinal) ||
|
|
!string.Equals(pointerValue.Length, "1", StringComparison.Ordinal))
|
|
{
|
|
detail = "last parameter is not a matching T / const T* len=1 pair";
|
|
return false;
|
|
}
|
|
|
|
detail = "verified T / const T* len=1 pair";
|
|
return true;
|
|
}
|
|
|
|
private static bool HaveEquivalentParameterTypes(
|
|
GlParameterDefinition left,
|
|
GlParameterDefinition right
|
|
)
|
|
{
|
|
return string.Equals(left.TypeName, right.TypeName, StringComparison.Ordinal) &&
|
|
left.DeclarationText.Contains('*', StringComparison.Ordinal) ==
|
|
right.DeclarationText.Contains('*', StringComparison.Ordinal);
|
|
}
|
|
|
|
private static string ReadRelationFamilyName(
|
|
string sourceName,
|
|
string targetName,
|
|
Dictionary<string, GlFunctionNameShape> shapes
|
|
)
|
|
{
|
|
if (shapes.TryGetValue(sourceName, out GlFunctionNameShape? sourceShape))
|
|
{
|
|
return sourceShape.FamilyName;
|
|
}
|
|
|
|
if (shapes.TryGetValue(targetName, out GlFunctionNameShape? targetShape))
|
|
{
|
|
return targetShape.FamilyName;
|
|
}
|
|
|
|
string source = RemoveGlPrefix(sourceName);
|
|
string target = RemoveGlPrefix(targetName);
|
|
if (target.EndsWith('v') &&
|
|
string.Equals(source, target[..^1], StringComparison.Ordinal))
|
|
{
|
|
return CSharpIdentifierPolicy.NormalizeMemberName(source);
|
|
}
|
|
|
|
int length = 0;
|
|
int maximum = Math.Min(source.Length, target.Length);
|
|
while (length < maximum && source[length] == target[length])
|
|
{
|
|
length++;
|
|
}
|
|
|
|
string familyName = length == 0 ? source : source[..length].TrimEnd('_');
|
|
return CSharpIdentifierPolicy.NormalizeMemberName(familyName);
|
|
}
|
|
|
|
private static string RemoveGlPrefix(
|
|
string name
|
|
)
|
|
{
|
|
return name.StartsWith("gl", StringComparison.Ordinal) && name.Length > 2
|
|
? name[2..]
|
|
: name;
|
|
}
|
|
|
|
private static FamilyBuilder ReadBuilder(
|
|
IDictionary<string, FamilyBuilder> builders,
|
|
string familyName
|
|
)
|
|
{
|
|
if (!builders.TryGetValue(familyName, out FamilyBuilder? builder))
|
|
{
|
|
builder = new FamilyBuilder(familyName);
|
|
builders.Add(familyName, builder);
|
|
}
|
|
|
|
return builder;
|
|
}
|
|
|
|
private static ReadOnlyCollection<T> Freeze<T>(
|
|
IEnumerable<T> values
|
|
)
|
|
{
|
|
return Array.AsReadOnly(values.ToArray());
|
|
}
|
|
|
|
private sealed class FamilyBuilder
|
|
{
|
|
|
|
private readonly Dictionary<string, GlFunctionFamilyMember> _members = new Dictionary<string, GlFunctionFamilyMember>(
|
|
StringComparer.Ordinal);
|
|
|
|
public FamilyBuilder(
|
|
string name
|
|
)
|
|
{
|
|
Name = name;
|
|
}
|
|
|
|
public string Name { get; }
|
|
|
|
public Dictionary<string, GlFunctionFamilyMember>.ValueCollection Members =>
|
|
_members.Values;
|
|
|
|
public List<GlFunctionRelation> Relations { get; } = new List<GlFunctionRelation>();
|
|
|
|
public List<string> Diagnostics { get; } = new List<string>();
|
|
|
|
public void Add(
|
|
GlCommandDefinition command,
|
|
GlFunctionNameShape? shape
|
|
)
|
|
{
|
|
_members.TryAdd(
|
|
command.Name,
|
|
new GlFunctionFamilyMember(command, shape));
|
|
}
|
|
|
|
public GlFunctionFamily Build()
|
|
{
|
|
GlFunctionFamilyMember[] members = _members.Values
|
|
.OrderBy(value => value.Command.Name, StringComparer.Ordinal)
|
|
.ToArray();
|
|
string[] diagnostics = Diagnostics
|
|
.Distinct(StringComparer.Ordinal)
|
|
.OrderBy(value => value, StringComparer.Ordinal)
|
|
.ToArray();
|
|
return new GlFunctionFamily(
|
|
Name,
|
|
Freeze(members),
|
|
Freeze(Relations.Distinct().ToArray()),
|
|
Freeze(diagnostics),
|
|
ReadShapeNamePolicy(members));
|
|
}
|
|
|
|
private static GlFunctionShapeNamePolicy ReadShapeNamePolicy(
|
|
IReadOnlyCollection<GlFunctionFamilyMember> members
|
|
)
|
|
{
|
|
GlFunctionFamilyMember[] scalarMembers = members
|
|
.Where(
|
|
value => value.NameShape is
|
|
{ IsVectorForm: false, Shape: not null })
|
|
.ToArray();
|
|
if (scalarMembers.Select(value => value.NameShape!.Shape)
|
|
.Distinct(StringComparer.Ordinal)
|
|
.Count() < 2)
|
|
{
|
|
return GlFunctionShapeNamePolicy.NotApplicable;
|
|
}
|
|
|
|
bool collision = scalarMembers
|
|
.GroupBy(ReadNativeSignature, StringComparer.Ordinal)
|
|
.Any(group => group.Select(value => value.NameShape!.Shape)
|
|
.Distinct(StringComparer.Ordinal)
|
|
.Count() > 1);
|
|
return collision
|
|
? GlFunctionShapeNamePolicy.MustRetain
|
|
: GlFunctionShapeNamePolicy.CanRemove;
|
|
}
|
|
|
|
private static string ReadNativeSignature(
|
|
GlFunctionFamilyMember member
|
|
)
|
|
{
|
|
GlCommandDefinition command = member.Command;
|
|
return $"{command.Return.TypeName}:" + string.Join(
|
|
",",
|
|
command.Parameters.Select(
|
|
value => $"{value.TypeName}:{value.DeclarationText.Contains('*', StringComparison.Ordinal)}"));
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|