utils/ast-colors-generator.psm1
|
using namespace System.Management.Automation.Language using namespace System.Drawing # Class that builds a deterministic, random-looking color map for all PowerShell AST node types. # Uses custom FNV-1a hash for stability across runs and machines. class AstColorsGenerator { [hashtable] $ColorsMap AstColorsGenerator() { [System.Reflection.Assembly]::LoadWithPartialName('System.Management.Automation') | Out-Null $this.ColorsMap = $this.BuildColorsMap() } [hashtable] GetColorsMap() { return $this.ColorsMap } [System.Type[]] GetAstTypes() { return [Ast].Assembly.GetTypes() | Where-Object { $_.IsPublic -and -not $_.IsAbstract -and [Ast].IsAssignableFrom($_) } | Sort-Object FullName } [int] GetAstDepth([System.Type] $Type) { $depth = 0 $t = $Type while ($t -and $t -ne [Ast]) { $t = $t.BaseType $depth++ } return $depth } # --- FNV-1a 32-bit hash --- [uint32] GetStableHash([string] $Key) { $bytes = [System.Text.Encoding]::ASCII.GetBytes($Key) $hash64 = [uint64]2166136261 foreach ($b in $bytes) { $hash64 = $hash64 -bxor [uint64]$b $hash64 = ($hash64 * [uint64]16777619) % [uint64]4294967296 } return [uint32]$hash64 } # --- HSL → RGB --- [Color] NewHslColor([double] $H, [double] $S, [double] $L) { function Hue2Rgb([double]$p, [double]$q, [double]$t) { if ($t -lt 0) { $t += 1 } if ($t -gt 1) { $t -= 1 } if ($t -lt 1 / 6) { return $p + ($q - $p) * 6 * $t } if ($t -lt 1 / 2) { return $q } if ($t -lt 2 / 3) { return $p + ($q - $p) * (2 / 3 - $t) * 6 } return $p } if ($S -eq 0) { $r = $g = $b = [math]::Round($L * 255) } else { $q = if ($L -lt 0.5) { $L * (1 + $S) } else { $L + $S - $L * $S } $p = 2 * $L - $q $r = [math]::Round(255 * (Hue2Rgb $p $q ($H + 1 / 3))) $g = [math]::Round(255 * (Hue2Rgb $p $q ($H))) $b = [math]::Round(255 * (Hue2Rgb $p $q ($H - 1 / 3))) } return [Color]::FromArgb($r, $g, $b) } # --- Deterministic color generator (bright & high-contrast) --- [System.Drawing.Color] NewDeterministicColorFromName([string] $Name, [int] $Depth = 0) { $hash32 = $this.GetStableHash($Name) # Even hue distribution via golden angle $index = [int]($hash32 % 1000) $goldenAngle = 137.508 $h = (($index * $goldenAngle) % 360.0) / 360.0 # Higher saturation, lower lightness for white background <# $s = 0.75 + ((($hash32 -shr 8) -band 0xFF) / 255.0) * 0.25 # 0.75–1.0 $l = 0.25 + ((($hash32 -shr 16) -band 0xFF) / 255.0) * 0.20 # 0.25–0.45 #> $s = 0.75 + ((($hash32 -shr 8) -band 0xFF) / 255.0) * 0.25 # 0.75–1.0 $l = 0.18 + ((($hash32 -shr 16) -band 0xFF) / 255.0) * 0.18 # 0.18–0.36 # Alternate brightness for parent/child contrast if ($Depth % 2 -eq 0) { $l = [math]::Max(0.22, $l - 0.05) } else { $l = [math]::Min(0.50, $l + 0.05) } return $this.NewHslColor($h, $s, $l) } # --- Build the AST color map --- [hashtable] BuildColorsMap() { $map = @{} $types = $this.GetAstTypes() foreach ($t in $types) { $depth = $this.GetAstDepth($t) $color = $this.NewDeterministicColorFromName($t.Name, $depth) $html = "#{0:X2}{1:X2}{2:X2}" -f $color.R, $color.G, $color.B $map[$t.Name] = $html } return $map } } # Example usage: # $gen = [AstColorsGenerator]::new() # $map = $gen.GetColorMap() # $map['ScriptBlockAst'] # $gen.GetColor('BinaryExpressionAst') |