11function Get-YamlMergeFingerprint {
22 <#
33 . SYNOPSIS
4- Creates a deterministic candidate index for structural merge comparisons.
4+ Creates a deterministic structural candidate index for merge comparisons.
55 #>
66 [CmdletBinding ()]
77 [OutputType ([string ])]
@@ -10,21 +10,247 @@ function Get-YamlMergeFingerprint {
1010 [pscustomobject ] $Node ,
1111
1212 [Parameter (Mandatory )]
13- [pscustomobject ] $State
13+ [pscustomobject ] $State ,
14+
15+ [Parameter ()]
16+ [AllowNull ()]
17+ [System.Collections.Generic.Dictionary [int , string ]] $Cache ,
18+
19+ [Parameter ()]
20+ [AllowNull ()]
21+ [pscustomobject ] $CandidateIndex
1422 )
1523
16- $effective = Get-YamlMergeNode - Node $Node
17- $tag = Get-YamlMergeNodeTag - Node $effective
18- if ($effective.Kind -eq ' Scalar' ) {
19- $resolved = (Resolve-YamlScalar - Node $effective ).Value
20- $value = Get-YamlScalarFingerprint - Value $resolved - Hasher $State.FingerprintHasher
21- return ' scalar:{0}:{1}:{2}' -f $tag.Length , $tag , $value
24+ if ($null -eq $Cache ) {
25+ $Cache = [System.Collections.Generic.Dictionary [int , string ]]::new()
2226 }
27+ $active = [System.Collections.Generic.HashSet [int ]]::new()
28+ $root = [pscustomobject ]@ { Value = ' ' ; IsContextual = $false }
29+ $stack = [System.Collections.Generic.Stack [object ]]::new()
30+ $stack.Push ([pscustomobject ]@ {
31+ Node = $Node
32+ Holder = $root
33+ State = ' Start'
34+ Index = 0
35+ Parts = $null
36+ Child = $null
37+ KeyHash = ' '
38+ Accumulator = $null
39+ IsContextual = $false
40+ })
41+
42+ while ($stack.Count -gt 0 ) {
43+ $frame = $stack.Peek ()
44+ if ($frame.State -eq ' Start' ) {
45+ $effective = $frame.Node
46+ while ($effective.Kind -eq ' Alias' ) {
47+ Add-YamlMergeWork - State $State.WorkState - Node $effective `
48+ - Operation ' fingerprint alias traversal'
49+ $effective = $effective.Target
50+ }
51+ Add-YamlMergeWork - State $State.WorkState - Node $effective `
52+ - Operation ' fingerprint node traversal'
53+ $frame.Node = $effective
54+
55+ if ($null -ne $CandidateIndex -and
56+ $CandidateIndex.Dependencies.Add ($effective.Id )) {
57+ $dependents = $null
58+ if (-not $State.IndexDependents.TryGetValue (
59+ $effective.Id ,
60+ [ref ] $dependents
61+ )) {
62+ $dependents = [System.Collections.Generic.List [object ]]::new()
63+ $State.IndexDependents [$effective.Id ] = $dependents
64+ }
65+ $dependents.Add ($CandidateIndex )
66+ }
67+
68+ $cached = ' '
69+ if ($Cache.TryGetValue ($effective.Id , [ref ] $cached )) {
70+ $frame.Holder.Value = $cached
71+ [void ] $stack.Pop ()
72+ continue
73+ }
2374
24- $count = if ($effective.Kind -eq ' Sequence' ) {
25- $effective.Items.Count
26- } else {
27- $effective.Entries.Count
75+ if (-not $active.Add ($effective.Id )) {
76+ $tag = Get-YamlMergeNodeTag - Node $effective
77+ $count = if ($effective.Kind -eq ' Sequence' ) {
78+ $effective.Items.Count
79+ } elseif ($effective.Kind -eq ' Mapping' ) {
80+ $effective.Entries.Count
81+ } else {
82+ 0
83+ }
84+ $frame.Holder.Value = Get-YamlFingerprintHash - Value (
85+ ' cycle:{0}:{1}:{2}:{3}' -f @ (
86+ $effective.Kind.ToLowerInvariant (),
87+ $tag.Length ,
88+ $tag ,
89+ $count
90+ )
91+ ) - Hasher $State.FingerprintHasher
92+ $frame.Holder.IsContextual = $true
93+ [void ] $stack.Pop ()
94+ continue
95+ }
96+
97+ if ($effective.Kind -eq ' Scalar' ) {
98+ $resolved = (Resolve-YamlScalar - Node $effective ).Value
99+ $tag = Get-YamlEffectiveTag - Node $effective - Value $resolved
100+ $value = Get-YamlScalarFingerprint - Value $resolved `
101+ - Hasher $State.FingerprintHasher
102+ $fingerprint = Get-YamlFingerprintHash - Value (
103+ ' scalar:{0}:{1}:{2}' -f $tag.Length , $tag , $value
104+ ) - Hasher $State.FingerprintHasher
105+ $Cache [$effective.Id ] = $fingerprint
106+ [void ] $active.Remove ($effective.Id )
107+ $frame.Holder.Value = $fingerprint
108+ [void ] $stack.Pop ()
109+ continue
110+ }
111+
112+ if ($effective.Kind -eq ' Sequence' ) {
113+ $frame.Parts = [System.Collections.Generic.List [string ]]::new()
114+ $frame.State = ' Sequence'
115+ } else {
116+ $frame.Accumulator = [int []]::new(32 )
117+ $frame.State = ' MappingKey'
118+ }
119+ continue
120+ }
121+
122+ if ($frame.State -eq ' Sequence' ) {
123+ if ($frame.Index -ge $frame.Node.Items.Count ) {
124+ $tag = Get-YamlEffectiveTag - Node $frame.Node - Value $null
125+ $canonical = if ($frame.IsContextual ) {
126+ ' cyclic-sequence:{0}:{1}:{2}' -f @ (
127+ $tag.Length ,
128+ $tag ,
129+ $frame.Node.Items.Count
130+ )
131+ } else {
132+ ' sequence:{0}:{1}:{2}' -f $tag.Length , $tag , ($frame.Parts -join ' |' )
133+ }
134+ $fingerprint = Get-YamlFingerprintHash - Value $canonical `
135+ - Hasher $State.FingerprintHasher
136+ if (-not $frame.IsContextual ) {
137+ $Cache [$frame.Node.Id ] = $fingerprint
138+ }
139+ [void ] $active.Remove ($frame.Node.Id )
140+ $frame.Holder.Value = $fingerprint
141+ $frame.Holder.IsContextual = $frame.IsContextual
142+ [void ] $stack.Pop ()
143+ continue
144+ }
145+ Add-YamlMergeWork - State $State.WorkState - Node $frame.Node `
146+ - Operation ' fingerprint sequence entry'
147+ $frame.Child = [pscustomobject ]@ { Value = ' ' ; IsContextual = $false }
148+ $frame.State = ' SequenceValue'
149+ $stack.Push ([pscustomobject ]@ {
150+ Node = $frame.Node.Items [$frame.Index ]
151+ Holder = $frame.Child
152+ State = ' Start'
153+ Index = 0
154+ Parts = $null
155+ Child = $null
156+ KeyHash = ' '
157+ Accumulator = $null
158+ IsContextual = $false
159+ })
160+ continue
161+ }
162+ if ($frame.State -eq ' SequenceValue' ) {
163+ $frame.Parts.Add ($frame.Child.Value )
164+ $frame.IsContextual = $frame.IsContextual -or $frame.Child.IsContextual
165+ $frame.Index ++
166+ $frame.State = ' Sequence'
167+ continue
168+ }
169+
170+ if ($frame.State -eq ' MappingKey' ) {
171+ if ($frame.Index -ge $frame.Node.Entries.Count ) {
172+ $tag = Get-YamlEffectiveTag - Node $frame.Node - Value $null
173+ $canonical = if ($frame.IsContextual ) {
174+ ' cyclic-mapping:{0}:{1}:{2}' -f @ (
175+ $tag.Length ,
176+ $tag ,
177+ $frame.Node.Entries.Count
178+ )
179+ } else {
180+ $aggregate = [byte []]::new($frame.Accumulator.Count )
181+ for ($index = 0 ; $index -lt $aggregate.Count ; $index ++ ) {
182+ $aggregate [$index ] = [byte ] $frame.Accumulator [$index ]
183+ }
184+ ' mapping:{0}:{1}:{2}:{3}' -f @ (
185+ $tag.Length ,
186+ $tag ,
187+ $frame.Node.Entries.Count ,
188+ [System.Convert ]::ToBase64String($aggregate )
189+ )
190+ }
191+ $fingerprint = Get-YamlFingerprintHash - Value $canonical `
192+ - Hasher $State.FingerprintHasher
193+ if (-not $frame.IsContextual ) {
194+ $Cache [$frame.Node.Id ] = $fingerprint
195+ }
196+ [void ] $active.Remove ($frame.Node.Id )
197+ $frame.Holder.Value = $fingerprint
198+ $frame.Holder.IsContextual = $frame.IsContextual
199+ [void ] $stack.Pop ()
200+ continue
201+ }
202+ Add-YamlMergeWork - State $State.WorkState - Node $frame.Node `
203+ - Operation ' fingerprint mapping entry'
204+ $frame.Child = [pscustomobject ]@ { Value = ' ' ; IsContextual = $false }
205+ $frame.State = ' MappingKeyValue'
206+ $stack.Push ([pscustomobject ]@ {
207+ Node = $frame.Node.Entries [$frame.Index ].Key
208+ Holder = $frame.Child
209+ State = ' Start'
210+ Index = 0
211+ Parts = $null
212+ Child = $null
213+ KeyHash = ' '
214+ Accumulator = $null
215+ IsContextual = $false
216+ })
217+ continue
218+ }
219+ if ($frame.State -eq ' MappingKeyValue' ) {
220+ $frame.KeyHash = $frame.Child.Value
221+ $frame.IsContextual = $frame.IsContextual -or $frame.Child.IsContextual
222+ $frame.Child = [pscustomobject ]@ { Value = ' ' ; IsContextual = $false }
223+ $frame.State = ' MappingValue'
224+ $stack.Push ([pscustomobject ]@ {
225+ Node = $frame.Node.Entries [$frame.Index ].Value
226+ Holder = $frame.Child
227+ State = ' Start'
228+ Index = 0
229+ Parts = $null
230+ Child = $null
231+ KeyHash = ' '
232+ Accumulator = $null
233+ IsContextual = $false
234+ })
235+ continue
236+ }
237+ if ($frame.State -eq ' MappingValue' ) {
238+ Add-YamlMergeWork - State $State.WorkState - Node $frame.Node `
239+ - Operation ' fingerprint mapping combination'
240+ $entryHash = Get-YamlFingerprintHash - Value (
241+ " entry:$ ( $frame.KeyHash ) =$ ( $frame.Child.Value ) "
242+ ) - Hasher $State.FingerprintHasher
243+ $entryBytes = [System.Convert ]::FromBase64String($entryHash )
244+ for ($index = 0 ; $index -lt $entryBytes.Count ; $index ++ ) {
245+ $frame.Accumulator [$index ] = (
246+ $frame.Accumulator [$index ] + $entryBytes [$index ]
247+ ) -band 0xff
248+ }
249+ $frame.IsContextual = $frame.IsContextual -or $frame.Child.IsContextual
250+ $frame.Index ++
251+ $frame.State = ' MappingKey'
252+ }
28253 }
29- return ' {0}:{1}:{2}:{3}' -f $effective.Kind.ToLowerInvariant (), $tag.Length , $tag , $count
254+
255+ $root.Value
30256}
0 commit comments