// -----------------------------------------------------------------------------
// file="FunctionFamilyCli"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using StgSharp.GenerateGL.Analysis;
namespace StgSharp.GenerateGL
{
///
/// Presents the function-family analysis without affecting generation.
///
internal static class FunctionFamilyCli
{
private const int ShowAllResults = int.MaxValue;
private const int QueryErrorExitCode = 1;
private const int SuccessExitCode = 0;
private const int UsageErrorExitCode = 2;
public static int Execute(
GlFunctionFamilyAnalysis analysis,
IReadOnlyList arguments
)
{
ArgumentNullException.ThrowIfNull(analysis);
ArgumentNullException.ThrowIfNull(arguments);
if (arguments.Count == 0)
{
WriteHelp();
return SuccessExitCode;
}
string command = arguments[0].ToUpperInvariant();
string[] queryArguments = arguments.Skip(1).ToArray();
return command switch
{
"SUMMARY" => QuerySummary(analysis, queryArguments),
"LIST" or "LS" => QueryList(analysis, queryArguments),
"SHOW" or "INFO" => QueryShow(analysis, queryArguments),
"RELATIONS" or "PAIRS" => QueryRelations(analysis, queryArguments),
"UNRESOLVED" or "UNCLASSIFIED" => QueryUnclassified(
analysis,
queryArguments),
"CONFLICTS" or "CONSTRAINTS" => QueryConstraints(
analysis,
queryArguments),
"HELP" or "?" => WriteHelpResult(queryArguments),
_ => WriteUsageError($"Unknown family command '{arguments[0]}'."),
};
}
public static void WriteHelp()
{
Console.WriteLine("Function-family analysis commands:");
Console.WriteLine(" families summary");
Console.WriteLine(" families list [filter] [--showcount ]");
Console.WriteLine(" families show ");
Console.WriteLine(" families relations [filter] [--showcount ]");
Console.WriteLine(" families unresolved [filter] [--showcount ]");
Console.WriteLine(" families conflicts [filter] [--showcount ]");
}
private static int QuerySummary(
GlFunctionFamilyAnalysis analysis,
string[] arguments
)
{
if (arguments.Length != 0)
{
return WriteUsageError("families summary does not accept arguments.");
}
Console.WriteLine("Function-family analysis");
WriteProperty("families", analysis.Families.Count);
WriteProperty(
"classified commands",
analysis.Families.SelectMany(value => value.Members)
.Select(value => value.Command.Name)
.Distinct(StringComparer.Ordinal)
.Count());
WriteProperty("unclassified commands", analysis.UnclassifiedCommands.Count);
WriteProperty("relations", analysis.Relations.Count);
WriteProperty(
"registry vector relations",
analysis.Relations.Count(
value => value.Kind == GlFunctionRelationKind.RegistryVectorEquivalent));
WriteProperty(
"inferred len=1 relations",
analysis.Relations.Count(
value => value.Kind == GlFunctionRelationKind.InferredSingleValuePointer));
WriteProperty(
"verified relations",
analysis.Relations.Count(
value => value.State == GlFunctionRelationState.Verified));
WriteProperty(
"relations requiring review",
analysis.Relations.Count(
value => value.State != GlFunctionRelationState.Verified));
WriteProperty(
"shape suffix must remain",
analysis.Families.Count(
value => value.ShapeNamePolicy == GlFunctionShapeNamePolicy.MustRetain));
return SuccessExitCode;
}
private static int QueryList(
GlFunctionFamilyAnalysis analysis,
IReadOnlyList arguments
)
{
if (!TryReadFilterOptions(
arguments,
out string? filter,
out int limit,
out string? error))
{
return WriteUsageError(error);
}
GlFunctionFamily[] families = FilterFamilies(analysis, filter).ToArray();
if (families.Length == 0)
{
Console.Error.WriteLine(
filter is null
? "No function families were recognized."
: $"No function families contain '{filter}'.");
return QueryErrorExitCode;
}
int displayedCount = Math.Min(families.Length, limit);
for (int index = 0; index < displayedCount; index++)
{
GlFunctionFamily family = families[index];
Console.WriteLine($"[{index + 1}] {family.Name}");
WriteProperty("members", family.Members.Count);
WriteProperty(
"shapes",
JoinDistinct(
family.Members.Select(value => value.NameShape?.Shape)));
WriteProperty(
"types",
JoinDistinct(
family.Members.Select(value => value.NameShape?.TypeSuffix)));
WriteProperty(
"forms",
JoinDistinct(
family.Members.Where(value => value.NameShape is not null)
.Select(
value => value.NameShape!.IsVectorForm
? "pointer"
: "scalar")));
WriteProperty("relations", family.Relations.Count);
WriteProperty("shape name", ReadShapePolicy(family.ShapeNamePolicy));
}
WriteTruncation(families.Length, displayedCount);
return SuccessExitCode;
}
private static int QueryShow(
GlFunctionFamilyAnalysis analysis,
string[] arguments
)
{
if (arguments.Length != 1)
{
return WriteUsageError("families show requires one exact family name.");
}
GlFunctionFamily? family = analysis.Families.FirstOrDefault(
value => string.Equals(
value.Name,
arguments[0],
StringComparison.OrdinalIgnoreCase));
if (family is null)
{
Console.Error.WriteLine($"No function family named '{arguments[0]}'.");
Console.Error.WriteLine($"Try: families list {arguments[0]}");
return QueryErrorExitCode;
}
Console.WriteLine($"function family: {family.Name}");
WriteProperty("members", family.Members.Count);
WriteProperty("shape name", ReadShapePolicy(family.ShapeNamePolicy));
for (int index = 0; index < family.Members.Count; index++)
{
GlFunctionFamilyMember member = family.Members[index];
Console.WriteLine($" [{index + 1}] {member.Command.Name}");
if (member.NameShape is not null)
{
if (member.NameShape.Shape is not null)
{
WriteIndentedProperty("shape", member.NameShape.Shape, 8);
}
WriteIndentedProperty(
"type suffix",
string.IsNullOrWhiteSpace(member.NameShape.TypeSuffix)
? "-"
: member.NameShape.TypeSuffix,
8);
WriteIndentedProperty(
"form",
member.NameShape.IsVectorForm ? "pointer" : "scalar",
8);
}
WriteIndentedProperty(
"parameters",
ReadParameterList(member.Command),
8);
}
WriteRelations(family.Relations);
if (family.Diagnostics.Count != 0)
{
Console.WriteLine(" diagnostics:");
foreach (string diagnostic in family.Diagnostics)
{
Console.WriteLine($" - {diagnostic}");
}
}
return SuccessExitCode;
}
private static int QueryRelations(
GlFunctionFamilyAnalysis analysis,
IReadOnlyList arguments
)
{
if (!TryReadFilterOptions(
arguments,
out string? filter,
out int limit,
out string? error))
{
return WriteUsageError(error);
}
GlFunctionRelation[] relations = analysis.Relations
.Where(
value => filter is null ||
value.SourceName.Contains(
filter,
StringComparison.OrdinalIgnoreCase) ||
value.TargetName.Contains(
filter,
StringComparison.OrdinalIgnoreCase))
.OrderBy(value => value.SourceName, StringComparer.Ordinal)
.ThenBy(value => value.TargetName, StringComparer.Ordinal)
.ToArray();
if (relations.Length == 0)
{
Console.Error.WriteLine(
filter is null
? "No function relations were recognized."
: $"No function relations contain '{filter}'.");
return QueryErrorExitCode;
}
int displayedCount = Math.Min(relations.Length, limit);
for (int index = 0; index < displayedCount; index++)
{
WriteRelation(index + 1, relations[index]);
}
WriteTruncation(relations.Length, displayedCount);
return SuccessExitCode;
}
private static int QueryUnclassified(
GlFunctionFamilyAnalysis analysis,
IReadOnlyList arguments
)
{
if (!TryReadFilterOptions(
arguments,
out string? filter,
out int limit,
out string? error))
{
return WriteUsageError(error);
}
string[] commands = analysis.UnclassifiedCommands
.Select(value => value.Name)
.Where(
value => filter is null || value.Contains(
filter,
StringComparison.OrdinalIgnoreCase))
.ToArray();
if (commands.Length == 0)
{
Console.Error.WriteLine(
filter is null
? "Every command belongs to a candidate family."
: $"No unclassified commands contain '{filter}'.");
return QueryErrorExitCode;
}
int displayedCount = Math.Min(commands.Length, limit);
for (int index = 0; index < displayedCount; index++)
{
Console.WriteLine($"[{index + 1}] {commands[index]}");
}
WriteTruncation(commands.Length, displayedCount);
return SuccessExitCode;
}
private static int QueryConstraints(
GlFunctionFamilyAnalysis analysis,
IReadOnlyList arguments
)
{
if (!TryReadFilterOptions(
arguments,
out string? filter,
out int limit,
out string? error))
{
return WriteUsageError(error);
}
GlFunctionFamily[] families = FilterFamilies(analysis, filter)
.Where(
value => value.ShapeNamePolicy == GlFunctionShapeNamePolicy.MustRetain ||
value.Diagnostics.Count != 0 ||
value.Relations.Any(
relation => relation.State != GlFunctionRelationState.Verified))
.ToArray();
if (families.Length == 0)
{
Console.Error.WriteLine(
filter is null
? "No family constraints require review."
: $"No constrained families contain '{filter}'.");
return QueryErrorExitCode;
}
int displayedCount = Math.Min(families.Length, limit);
for (int index = 0; index < displayedCount; index++)
{
GlFunctionFamily family = families[index];
Console.WriteLine($"[{index + 1}] {family.Name}");
if (family.ShapeNamePolicy == GlFunctionShapeNamePolicy.MustRetain)
{
WriteProperty(
"constraint",
"shape suffix must remain because normalized signatures collide");
}
foreach (string diagnostic in family.Diagnostics)
{
WriteProperty("diagnostic", diagnostic);
}
}
WriteTruncation(families.Length, displayedCount);
return SuccessExitCode;
}
private static IEnumerable FilterFamilies(
GlFunctionFamilyAnalysis analysis,
string? filter
)
{
return analysis.Families.Where(
value => filter is null || value.Name.Contains(
filter,
StringComparison.OrdinalIgnoreCase));
}
private static bool TryReadFilterOptions(
IReadOnlyList arguments,
out string? filter,
out int limit,
out string? error
)
{
filter = null;
limit = ShowAllResults;
int index = 0;
if (index < arguments.Count && !arguments[index].StartsWith(
"--",
StringComparison.Ordinal))
{
filter = arguments[index++];
}
while (index < arguments.Count)
{
string argument = arguments[index++];
if ((argument == "--showcount" || argument == "--limit") &&
index < arguments.Count && int.TryParse(
arguments[index++],
NumberStyles.None,
CultureInfo.InvariantCulture,
out limit) && limit > 0)
{
continue;
}
error = $"Unknown or invalid family option '{argument}'.";
return false;
}
error = null;
return true;
}
private static string ReadParameterList(
Registry.GlCommandDefinition command
)
{
return string.Join(
", ",
command.Parameters.Select(value => value.DeclarationText));
}
private static void WriteRelations(
IReadOnlyList relations
)
{
if (relations.Count == 0)
{
return;
}
Console.WriteLine(" relations:");
for (int index = 0; index < relations.Count; index++)
{
WriteRelation(index + 1, relations[index], 4);
}
}
private static void WriteRelation(
int index,
GlFunctionRelation relation,
int indentation = 0
)
{
string prefix = new string(' ', indentation);
Console.WriteLine(
$"{prefix}[{index}] {relation.SourceName} -> {relation.TargetName}");
WriteIndentedProperty(
"source",
relation.Kind == GlFunctionRelationKind.RegistryVectorEquivalent
? "gl.xml vecequiv"
: "inferred len=1 pair",
indentation + 4);
WriteIndentedProperty(
"state",
ReadRelationState(relation.State),
indentation + 4);
WriteIndentedProperty("detail", relation.Detail, indentation + 4);
}
private static string ReadShapePolicy(
GlFunctionShapeNamePolicy policy
)
{
return policy switch
{
GlFunctionShapeNamePolicy.NotApplicable => "not applicable",
GlFunctionShapeNamePolicy.CanRemove => "may be removed",
GlFunctionShapeNamePolicy.MustRetain => "must remain",
_ => throw new ArgumentOutOfRangeException(nameof(policy)),
};
}
private static string ReadRelationState(
GlFunctionRelationState state
)
{
return state switch
{
GlFunctionRelationState.Verified => "verified",
GlFunctionRelationState.MissingTarget => "missing target",
GlFunctionRelationState.Rejected => "rejected",
_ => throw new ArgumentOutOfRangeException(nameof(state)),
};
}
private static string JoinDistinct(
IEnumerable values
)
{
string[] distinct = values.Where(value => !string.IsNullOrWhiteSpace(value))
.Select(value => value!)
.Distinct(StringComparer.Ordinal)
.OrderBy(value => value, StringComparer.Ordinal)
.ToArray();
return distinct.Length == 0 ? "-" : string.Join(", ", distinct);
}
private static int WriteHelpResult(
string[] arguments
)
{
if (arguments.Length != 0)
{
return WriteUsageError("families help does not accept arguments.");
}
WriteHelp();
return SuccessExitCode;
}
private static int WriteUsageError(
string? error
)
{
Console.Error.WriteLine($"error: {error}");
return UsageErrorExitCode;
}
private static void WriteProperty(
string name,
object value
)
{
WriteIndentedProperty(name, value, 2);
}
private static void WriteIndentedProperty(
string name,
object value,
int indentation
)
{
Console.WriteLine($"{new string(' ', indentation)}{name}: {value}");
}
private static void WriteTruncation(
int totalCount,
int displayedCount
)
{
if (displayedCount != totalCount)
{
Console.WriteLine($"... {totalCount - displayedCount} more result(s)");
}
}
}
}