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